aspera-cli 4.26.1 → 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 +19 -1
- 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 +43 -36
- data/lib/aspera/api/cos_node.rb +1 -1
- data/lib/aspera/api/faspex.rb +7 -6
- data/lib/aspera/api/node.rb +7 -7
- data/lib/aspera/ascmd.rb +4 -4
- data/lib/aspera/ascp/installation.rb +6 -2
- data/lib/aspera/cli/context.rb +58 -0
- data/lib/aspera/cli/extended_value.rb +3 -2
- data/lib/aspera/cli/formatter.rb +83 -184
- data/lib/aspera/cli/http.rb +167 -0
- data/lib/aspera/cli/manager.rb +3 -2
- data/lib/aspera/cli/plugins/alee.rb +2 -2
- data/lib/aspera/cli/plugins/aoc.rb +78 -67
- data/lib/aspera/cli/plugins/ats.rb +16 -16
- data/lib/aspera/cli/plugins/base.rb +18 -12
- data/lib/aspera/cli/plugins/config.rb +166 -438
- 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 +21 -21
- data/lib/aspera/cli/plugins/faspex5.rb +64 -44
- 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 +79 -79
- 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} +14 -155
- data/lib/aspera/cli/sync_actions.rb +14 -10
- data/lib/aspera/cli/terminal_formatter.rb +65 -0
- 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 +1 -1
- data/lib/aspera/data_repository.rb +4 -0
- data/lib/aspera/dot_container.rb +1 -1
- data/lib/aspera/environment.rb +22 -3
- data/lib/aspera/faspex_gw.rb +1 -1
- 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 +6 -2
- 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 +37 -13
- 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 +3 -3
- data/lib/aspera/rest.rb +8 -4
- 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 +14 -4
- data/lib/aspera/schema/IBM Aspera on Cloud API-0.2.6-enhanced.yaml +827 -88
- data/lib/aspera/schema/async_tables.yaml +361 -0
- data/lib/aspera/schema/reader.rb +1 -1
- data/lib/aspera/schema/registry.rb +13 -6
- data/lib/aspera/secret_hider.rb +4 -0
- data/lib/aspera/sync/database.rb +14 -10
- data/lib/aspera/sync/operations.rb +5 -5
- data/lib/aspera/temp_file_manager.rb +4 -0
- data/lib/aspera/transfer/parameters.rb +4 -4
- data/lib/aspera/transfer/resumer.rb +1 -1
- data/lib/aspera/transfer/spec.schema.yaml +17 -8
- data/lib/aspera/transfer/uri.rb +1 -1
- data.tar.gz.sig +0 -0
- metadata +21 -14
- metadata.gz.sig +0 -0
|
@@ -1,14 +1,16 @@
|
|
|
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'
|
|
@@ -19,55 +21,8 @@ require 'openssl'
|
|
|
19
21
|
|
|
20
22
|
module Aspera
|
|
21
23
|
module Cli
|
|
22
|
-
# Global objects shared with plugins
|
|
23
|
-
class Context
|
|
24
|
-
# @type [Array<Symbol>]
|
|
25
|
-
MEMBERS = %i[options transfer config formatter persistency man_header].freeze
|
|
26
|
-
# @!attribute [rw] options
|
|
27
|
-
# @return [Manager] the command line options manager
|
|
28
|
-
# @!attribute [rw] transfer
|
|
29
|
-
# @return [TransferAgent] the transfer agent, used by transfer plugins
|
|
30
|
-
# @!attribute [rw] config
|
|
31
|
-
# @return [Plugins::Config] the configuration plugin, used by plugins to get configuration values and presets
|
|
32
|
-
# @!attribute [rw] formatter
|
|
33
|
-
# @return [Formatter] the formatter, used by plugins to display results and messages
|
|
34
|
-
# @!attribute [rw] persistency
|
|
35
|
-
# @return [Object] # whatever the type is
|
|
36
|
-
# @!attribute [rw] man_header
|
|
37
|
-
# @return [Boolean] whether to display the manual header in plugin help
|
|
38
|
-
attr_accessor(*MEMBERS)
|
|
39
|
-
|
|
40
|
-
# Initialize all members to nil, so that they are defined and can be validated later
|
|
41
|
-
# @return [nil]
|
|
42
|
-
def initialize
|
|
43
|
-
MEMBERS.each{ |i| instance_variable_set(:"@#{i}", nil)}
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
# Validate that all members are set, raise exception if not
|
|
47
|
-
# @raise [Aspera::AssertionError] if any member is not set
|
|
48
|
-
# @return [nil]
|
|
49
|
-
def validate
|
|
50
|
-
MEMBERS.each do |i|
|
|
51
|
-
Aspera.assert(instance_variable_defined?(:"@#{i}"))
|
|
52
|
-
Aspera.assert(!instance_variable_get(:"@#{i}").nil?)
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
# Check if the context is in manual-only mode
|
|
57
|
-
# @return [Boolean] true if in manual-only mode
|
|
58
|
-
def only_manual?
|
|
59
|
-
transfer.eql?(:only_manual)
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
# Set the context to manual-only mode
|
|
63
|
-
# @return [Symbol] :only_manual
|
|
64
|
-
def only_manual!
|
|
65
|
-
@transfer = :only_manual
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
|
|
69
24
|
# The main CLI class
|
|
70
|
-
class
|
|
25
|
+
class Runner
|
|
71
26
|
# Plugins store transfer result using this key and use result_transfer_multiple()
|
|
72
27
|
STATUS_FIELD = 'status'
|
|
73
28
|
COMMAND_CONFIG = :config
|
|
@@ -79,46 +34,19 @@ module Aspera
|
|
|
79
34
|
private_constant :COMMAND_CONFIG, :COMMAND_HELP, :SCALAR_TYPES, :USER_INTERFACES
|
|
80
35
|
|
|
81
36
|
class << self
|
|
82
|
-
# Create a special result type (only used internally here)
|
|
83
|
-
# @param special_sym [Symbol] the special result type
|
|
84
|
-
# @return [Hash] result hash with type :special
|
|
85
|
-
def result_special(special_sym); {type: :special, data: special_sym}; end
|
|
86
|
-
|
|
87
|
-
# Expect some list, but nothing to display
|
|
88
|
-
# @return [Hash] result hash for empty list
|
|
89
|
-
def result_empty; result_special(:empty); end
|
|
90
|
-
|
|
91
|
-
# Nothing expected
|
|
92
|
-
# @return [Hash] result hash for nothing
|
|
93
|
-
def result_nothing; result_special(:nothing); end
|
|
94
|
-
|
|
95
|
-
# Result is some status, such as "complete", "deleted"...
|
|
96
|
-
# @param status [String] The status
|
|
97
|
-
# @return [Hash] result hash with type :status
|
|
98
|
-
def result_status(status); return {type: :status, data: status}; end
|
|
99
|
-
|
|
100
|
-
# Text result coming from command result
|
|
101
|
-
# @param data [String, Integer, Symbol] the text data to display
|
|
102
|
-
# @return [Hash] result hash with type :text
|
|
103
|
-
def result_text(data); return {type: :text, data: data}; end
|
|
104
|
-
|
|
105
|
-
# Create a success result
|
|
106
|
-
# @return [Hash] result hash with status 'complete'
|
|
107
|
-
def result_success; return result_status('complete'); end
|
|
108
|
-
|
|
109
37
|
# Process statuses of finished transfer sessions
|
|
110
38
|
# @param statuses [Array] array of transfer session statuses
|
|
111
39
|
# @raise [Symbol] exception if there is one error
|
|
112
|
-
# @return [
|
|
40
|
+
# @return [Result] empty status result if all transfers succeeded
|
|
113
41
|
def result_transfer(statuses)
|
|
114
42
|
worst = TransferAgent.session_status(statuses)
|
|
115
43
|
raise worst unless worst.eql?(:success)
|
|
116
|
-
return
|
|
44
|
+
return Result::Nothing.new
|
|
117
45
|
end
|
|
118
46
|
|
|
119
47
|
# Used when one command executes several transfer jobs (each job being possibly multi session)
|
|
120
48
|
# @param status_table [Array] [{STATUS_FIELD=>[status array],...},...]
|
|
121
|
-
# @return a status object suitable as command result
|
|
49
|
+
# @return [Result] a status object suitable as command result
|
|
122
50
|
# Each element has a key STATUS_FIELD which contains the result of possibly multiple sessions
|
|
123
51
|
def result_transfer_multiple(status_table)
|
|
124
52
|
global_status = :success
|
|
@@ -129,62 +57,7 @@ module Aspera
|
|
|
129
57
|
item[STATUS_FIELD] = item[STATUS_FIELD].join(',')
|
|
130
58
|
end
|
|
131
59
|
raise global_status unless global_status.eql?(:success)
|
|
132
|
-
return
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
# Display image for that URL or directly blob
|
|
136
|
-
# @param url_or_blob [String] URL or blob to display as image
|
|
137
|
-
# @return [Hash] result hash with type :image
|
|
138
|
-
def result_image(url_or_blob)
|
|
139
|
-
return {type: :image, data: url_or_blob}
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
# A single object, must be Hash
|
|
143
|
-
# @param data [Hash] the object data
|
|
144
|
-
# @param fields [Array<String>, nil] optional list of fields to display
|
|
145
|
-
# @return [Hash] result hash with type :single_object
|
|
146
|
-
def result_single_object(data, fields: nil)
|
|
147
|
-
return {type: :single_object, data: data, fields: fields}
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
# An Array of Hash
|
|
151
|
-
# @param data [Array<Hash>] array of objects
|
|
152
|
-
# @param fields [Array<String>, nil] optional list of fields to display
|
|
153
|
-
# @param total [Integer, nil] optional total count
|
|
154
|
-
# @return [Hash] result hash with type :object_list
|
|
155
|
-
def result_object_list(data, fields: nil, total: nil)
|
|
156
|
-
return {type: :object_list, data: data, fields: fields, total: total}
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
# A list of values
|
|
160
|
-
# @param data [Array] The list of values
|
|
161
|
-
# @param name [String] The name of the list (used for display)
|
|
162
|
-
# @return [Hash] result hash with type :value_list
|
|
163
|
-
def result_value_list(data, name: 'id')
|
|
164
|
-
Aspera.assert_type(data, Array)
|
|
165
|
-
Aspera.assert_type(name, String)
|
|
166
|
-
return {type: :value_list, data: data, name: name}
|
|
167
|
-
end
|
|
168
|
-
|
|
169
|
-
# Determines type of result based on data
|
|
170
|
-
# @param data [Object] the data to analyze and format
|
|
171
|
-
# @return [Hash] result hash with appropriate type based on data
|
|
172
|
-
def result_auto(data)
|
|
173
|
-
case data
|
|
174
|
-
when NilClass
|
|
175
|
-
return result_special(:null)
|
|
176
|
-
when Hash
|
|
177
|
-
return result_single_object(data)
|
|
178
|
-
when Array
|
|
179
|
-
all_types = data.map(&:class).uniq
|
|
180
|
-
return result_object_list(data) if all_types.eql?([Hash])
|
|
181
|
-
unsupported_types = all_types - SCALAR_TYPES
|
|
182
|
-
return result_value_list(data, name: 'list') if unsupported_types.empty?
|
|
183
|
-
Aspera.error_unexpected_value(unsupported_types){'list item types'}
|
|
184
|
-
when *SCALAR_TYPES
|
|
185
|
-
return result_text(data)
|
|
186
|
-
else Aspera.error_unexpected_value(data.class.name){'result type'}
|
|
187
|
-
end
|
|
60
|
+
return Result::ObjectList.new(status_table)
|
|
188
61
|
end
|
|
189
62
|
end
|
|
190
63
|
|
|
@@ -196,14 +69,13 @@ module Aspera
|
|
|
196
69
|
Log.dump(:argv, @argv, level: :trace2)
|
|
197
70
|
@option_help = false
|
|
198
71
|
@option_show_config = false
|
|
199
|
-
@bash_completion = false
|
|
200
72
|
@context = Context.new
|
|
201
73
|
end
|
|
202
74
|
|
|
203
75
|
# This is the main function called by initial script just after constructor
|
|
204
76
|
# Processes command line arguments, executes commands, and handles exceptions
|
|
205
77
|
# @return [nil]
|
|
206
|
-
def
|
|
78
|
+
def run
|
|
207
79
|
# Catch exception information , if any
|
|
208
80
|
exception_info = nil
|
|
209
81
|
# False if command shall not be executed (e.g. --show-config)
|
|
@@ -215,7 +87,6 @@ module Aspera
|
|
|
215
87
|
Plugins::Factory.instance.add_plugins_from_lookup_folders
|
|
216
88
|
# Help requested without command ? (plugins must be known here)
|
|
217
89
|
show_usage if @option_help && @context.options.command_or_arg_empty?
|
|
218
|
-
generate_bash_completion if @bash_completion
|
|
219
90
|
@context.config.periodic_check_newer_gem_version
|
|
220
91
|
command_sym =
|
|
221
92
|
if @option_show_config && @context.options.command_or_arg_empty?
|
|
@@ -240,7 +111,7 @@ module Aspera
|
|
|
240
111
|
# Help requested for current plugin
|
|
241
112
|
show_usage(all: false) if @option_help
|
|
242
113
|
if @option_show_config
|
|
243
|
-
@context.formatter.display_results(
|
|
114
|
+
@context.formatter.display_results(Result::SingleObject.new(@context.options.known_options(only_defined: true).stringify_keys))
|
|
244
115
|
execute_command = false
|
|
245
116
|
end
|
|
246
117
|
# Locking for single execution (only after "per plugin" option, in case lock port is there)
|
|
@@ -263,7 +134,7 @@ module Aspera
|
|
|
263
134
|
at_exit{File.delete(pid_file)}
|
|
264
135
|
end
|
|
265
136
|
# Execute and display (if not exclusive execution)
|
|
266
|
-
@context.formatter.display_results(
|
|
137
|
+
@context.formatter.display_results(command_plugin.execute_action) if execute_command
|
|
267
138
|
# Save config file if command modified it
|
|
268
139
|
@context.config.save_config_file_if_needed
|
|
269
140
|
# Finish
|
|
@@ -271,7 +142,7 @@ module Aspera
|
|
|
271
142
|
rescue Net::SSH::AuthenticationFailed => e; exception_info = {e: e, t: 'SSH', security: true}
|
|
272
143
|
rescue OpenSSL::SSL::SSLError => e; exception_info = {e: e, t: 'SSL'}
|
|
273
144
|
rescue Cli::BadArgument => e; exception_info = {e: e, t: 'Argument', usage: true}
|
|
274
|
-
rescue Cli::MissingArgument => e; exception_info = {e: e, t: 'Missing'
|
|
145
|
+
rescue Cli::MissingArgument => e; exception_info = {e: e, t: 'Missing'}
|
|
275
146
|
rescue Cli::BadIdentifier => e; exception_info = {e: e, t: 'Identifier'}
|
|
276
147
|
rescue Cli::SchemaRequest => e; exception_info = {e: e, t: 'Schema'}
|
|
277
148
|
rescue Cli::Error => e; exception_info = {e: e, t: 'Tool', usage: true}
|
|
@@ -299,7 +170,7 @@ module Aspera
|
|
|
299
170
|
Log.log.warn{'Sorry, no schema provided yet. Please refer to the manual or API.'}
|
|
300
171
|
else
|
|
301
172
|
builder = Schema::Documentation.new(TerminalFormatter, Schema::Registry.instance.reader(schema_path)).build
|
|
302
|
-
@context.formatter.display_results(
|
|
173
|
+
@context.formatter.display_results(Result::ObjectList.new(builder.rows, fields: builder.columns))
|
|
303
174
|
end
|
|
304
175
|
end
|
|
305
176
|
end
|
|
@@ -371,7 +242,7 @@ module Aspera
|
|
|
371
242
|
@context.config = Plugins::Config.new(context: @context)
|
|
372
243
|
@context.man_header = true
|
|
373
244
|
# Data persistency is set in config
|
|
374
|
-
Aspera.assert(@context.persistency
|
|
245
|
+
Aspera.assert(@context.persistency, 'missing persistency object')
|
|
375
246
|
# The TransferAgent plugin may use the @preset parser
|
|
376
247
|
@context.transfer = TransferAgent.new(@context.options, @context.config)
|
|
377
248
|
# Add commands for config plugin after all options have been added
|
|
@@ -422,7 +293,6 @@ module Aspera
|
|
|
422
293
|
def declare_global_options
|
|
423
294
|
Log.log.debug('declare_global_options')
|
|
424
295
|
@context.options.declare(:help, 'Show this message', allowed: Allowed::TYPES_NONE, short: 'h'){@option_help = true}
|
|
425
|
-
@context.options.declare(:bash_comp, 'Generate bash completion for command', allowed: Allowed::TYPES_NONE){@bash_completion = true}
|
|
426
296
|
@context.options.declare(:show_config, 'Display parameters used for the provided action', allowed: Allowed::TYPES_NONE){@option_show_config = true}
|
|
427
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
|
|
428
298
|
@context.options.declare(
|
|
@@ -464,17 +334,6 @@ module Aspera
|
|
|
464
334
|
command_plugin = Plugins::Factory.instance.create(plugin_name_sym, context: @context)
|
|
465
335
|
return command_plugin
|
|
466
336
|
end
|
|
467
|
-
|
|
468
|
-
# Generate bash completion suggestions
|
|
469
|
-
# @return [nil]
|
|
470
|
-
def generate_bash_completion
|
|
471
|
-
if @context.options.get_next_argument('', multiple: true, mandatory: false).nil?
|
|
472
|
-
Plugins::Factory.instance.plugin_list.each{ |p| puts p}
|
|
473
|
-
else
|
|
474
|
-
Log.log.warn('only first level completion so far')
|
|
475
|
-
end
|
|
476
|
-
Process.exit(0)
|
|
477
|
-
end
|
|
478
337
|
end
|
|
479
338
|
end
|
|
480
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
|
|
|
@@ -33,7 +34,7 @@ module Aspera
|
|
|
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
|
|
@@ -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
|
|
@@ -66,7 +66,7 @@ module Aspera
|
|
|
66
66
|
Aspera.assert(node.key?('type') || node.key?('enum')){"Missing type for #{name} in #{schema.current.dig('description').current}"}
|
|
67
67
|
Aspera.assert(node['type'].eql?('boolean')){"switch must be bool: #{name}"} if node['x-cli-switch'] && !node['x-cli-special']
|
|
68
68
|
node['x-cli-option'] = "--#{name.to_s.tr('_', '-')}" if node['x-cli-option'].eql?(true) || (node['x-cli-switch'].eql?(true) && !node.key?('x-cli-option'))
|
|
69
|
-
Aspera.assert(DIRECT_PROPERTIES.any?{ |i| node.key?(i)}, type: :warn)
|
|
69
|
+
Aspera.assert(DIRECT_PROPERTIES.any?{ |i| node.key?(i)}, name, type: :warn) if ascp && supported_by_agent(:direct, node)
|
|
70
70
|
node.freeze
|
|
71
71
|
end
|
|
72
72
|
schema
|
data/lib/aspera/dot_container.rb
CHANGED
|
@@ -39,7 +39,7 @@ module Aspera
|
|
|
39
39
|
# @param container [Hash, Array]
|
|
40
40
|
# @param index [String, Integer]
|
|
41
41
|
def array_requires_integer_index!(container, index)
|
|
42
|
-
Aspera.assert(container.is_a?(Hash) || index.is_a?(Integer)
|
|
42
|
+
Aspera.assert(container.is_a?(Hash) || index.is_a?(Integer), 'Using String index when Integer index used previously')
|
|
43
43
|
end
|
|
44
44
|
|
|
45
45
|
# Create a new `Hash` or `Array` depending on type of `key`
|
data/lib/aspera/environment.rb
CHANGED
|
@@ -13,6 +13,10 @@ require 'shellwords'
|
|
|
13
13
|
|
|
14
14
|
module Aspera
|
|
15
15
|
# detect OS, architecture, and specific stuff
|
|
16
|
+
#
|
|
17
|
+
# @!method self.instance
|
|
18
|
+
# Returns the singleton instance of Environment
|
|
19
|
+
# @return [Environment] the singleton instance
|
|
16
20
|
class Environment
|
|
17
21
|
include Singleton
|
|
18
22
|
|
|
@@ -111,7 +115,7 @@ module Aspera
|
|
|
111
115
|
# @raise [RuntimeError] If `:exception` is `true` and the process fails in `:capture` mode.
|
|
112
116
|
def secure_execute(*cmd, mode: :execute, **kwargs)
|
|
113
117
|
cmd = cmd.map(&:to_s)
|
|
114
|
-
Aspera.assert(cmd.size.positive?,
|
|
118
|
+
Aspera.assert(cmd.size.positive?, 'executable must be present', type: ArgumentError)
|
|
115
119
|
Aspera.assert_values(mode, PROCESS_MODES, type: ArgumentError){'mode'}
|
|
116
120
|
Log.log.debug do
|
|
117
121
|
parts = [mode.to_s, 'command:']
|
|
@@ -153,7 +157,7 @@ module Aspera
|
|
|
153
157
|
# @param mode [Integer] the file mode (permissions)
|
|
154
158
|
# @yieldreturn [String] The content to write to the file
|
|
155
159
|
def write_file_restricted(path, force: false, mode: nil)
|
|
156
|
-
Aspera.assert(block_given?, type: Aspera::InternalError)
|
|
160
|
+
Aspera.assert(block_given?, 'block required for write_file_restricted', type: Aspera::InternalError)
|
|
157
161
|
if force || !File.exist?(path)
|
|
158
162
|
# Windows may give error
|
|
159
163
|
File.unlink(path) rescue nil
|
|
@@ -192,10 +196,24 @@ module Aspera
|
|
|
192
196
|
end
|
|
193
197
|
|
|
194
198
|
# @return true if we can display Unicode characters
|
|
199
|
+
# Uses Encoding.locale_charmap for OS-independent detection.
|
|
200
|
+
# Falls back to locale env vars for systems where charmap is not available.
|
|
195
201
|
# https://www.gnu.org/software/libc/manual/html_node/Locale-Categories.html
|
|
196
202
|
# https://pubs.opengroup.org/onlinepubs/7908799/xbd/envvar.html
|
|
197
203
|
def terminal_supports_unicode?
|
|
198
|
-
|
|
204
|
+
return false unless terminal?
|
|
205
|
+
locale_charmap_utf8? || I18N_VARS.any?{ |var| ENV[var]&.include?('UTF-8')}
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
private
|
|
209
|
+
|
|
210
|
+
# @return true if the locale charmap resolves to UTF-8
|
|
211
|
+
# Unix: nl_langinfo(CODESET) returns "UTF-8"
|
|
212
|
+
# Windows: GetACP returns "CP65001" which Encoding.find resolves as UTF-8
|
|
213
|
+
def locale_charmap_utf8?
|
|
214
|
+
Encoding.find(Encoding.locale_charmap) == Encoding::UTF_8
|
|
215
|
+
rescue ArgumentError
|
|
216
|
+
false
|
|
199
217
|
end
|
|
200
218
|
end
|
|
201
219
|
attr_accessor :url_method, :file_illegal_characters
|
|
@@ -266,6 +284,7 @@ module Aspera
|
|
|
266
284
|
return unless @os.eql?(OS_WINDOWS) && ENV.key?('USERPROFILE') && Dir.exist?(ENV.fetch('USERPROFILE', nil))
|
|
267
285
|
ENV['HOME'] = ENV.fetch('USERPROFILE', nil)
|
|
268
286
|
Log.log.debug{"Windows: set HOME to USERPROFILE: #{Dir.home}"}
|
|
287
|
+
nil
|
|
269
288
|
end
|
|
270
289
|
|
|
271
290
|
def graphical?
|
data/lib/aspera/faspex_gw.rb
CHANGED
|
@@ -68,7 +68,7 @@ module Aspera
|
|
|
68
68
|
case request.path
|
|
69
69
|
when '/aspera/faspex/send'
|
|
70
70
|
begin
|
|
71
|
-
Aspera.assert(!request.body.nil
|
|
71
|
+
Aspera.assert(!request.body.nil?, 'payload missing')
|
|
72
72
|
faspex_pkg_parameters = JSON.parse(request.body)
|
|
73
73
|
Log.log.debug{"faspex pkg create parameters=#{faspex_pkg_parameters}"}
|
|
74
74
|
# compare string, as class is not yet known here
|
data/lib/aspera/json_rpc.rb
CHANGED
|
@@ -36,15 +36,16 @@ module Aspera
|
|
|
36
36
|
id: @request_id += 1
|
|
37
37
|
})
|
|
38
38
|
Aspera.assert_type(data, Hash){'response'}
|
|
39
|
-
Aspera.assert(data['jsonrpc'] == JSON_RPC_VERSION
|
|
40
|
-
Aspera.assert(data.key?('id')
|
|
41
|
-
Aspera.assert(!(data.key?('error') && data.key?('result'))
|
|
39
|
+
Aspera.assert(data['jsonrpc'] == JSON_RPC_VERSION, 'bad version in response')
|
|
40
|
+
Aspera.assert(data.key?('id'), 'missing id in response')
|
|
41
|
+
Aspera.assert(!(data.key?('error') && data.key?('result')), 'both error and response')
|
|
42
42
|
Aspera.assert(
|
|
43
43
|
!data.key?('error') ||
|
|
44
44
|
data['error'].is_a?(Hash) &&
|
|
45
45
|
data['error']['code'].is_a?(Integer) &&
|
|
46
|
-
data['error']['message'].is_a?(String)
|
|
47
|
-
|
|
46
|
+
data['error']['message'].is_a?(String),
|
|
47
|
+
'bad error response'
|
|
48
|
+
)
|
|
48
49
|
return data['result']
|
|
49
50
|
end
|
|
50
51
|
end
|
data/lib/aspera/keychain/base.rb
CHANGED
|
@@ -28,7 +28,7 @@ module Aspera
|
|
|
28
28
|
vault_file = File.read(@path)
|
|
29
29
|
if vault_file.start_with?('---')
|
|
30
30
|
vault_info = YAML.parse(vault_file).to_ruby
|
|
31
|
-
Aspera.assert(vault_info.keys.sort == FILE_KEYS
|
|
31
|
+
Aspera.assert(vault_info.keys.sort == FILE_KEYS, 'Invalid vault file')
|
|
32
32
|
@cipher_name = vault_info['cipher']
|
|
33
33
|
vault_encrypted_data = vault_info['data']
|
|
34
34
|
else
|
|
@@ -20,7 +20,7 @@ module Aspera
|
|
|
20
20
|
when 'file'
|
|
21
21
|
info[:file] = name || 'vault.bin'
|
|
22
22
|
info[:file] = File.join(folder, info[:file]) unless File.absolute_path?(info[:file])
|
|
23
|
-
Aspera.assert(!password.nil
|
|
23
|
+
Aspera.assert(!password.nil?, 'please provide password')
|
|
24
24
|
info[:password] = password
|
|
25
25
|
# this module requires compilation, so it is optional
|
|
26
26
|
require 'aspera/keychain/encrypted_hash'
|