aspera-cli 4.27.1 → 4.27.3
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 +67 -1
- data/bin/ascli +2 -1
- data/docs/README.md +805 -747
- data/lib/aspera/agent/connect.rb +6 -4
- data/lib/aspera/agent/desktop.rb +2 -2
- data/lib/aspera/agent/direct.rb +3 -1
- data/lib/aspera/agent/node.rb +3 -3
- data/lib/aspera/api/alee.rb +1 -1
- data/lib/aspera/api/aoc.rb +14 -12
- data/lib/aspera/api/ats.rb +1 -1
- data/lib/aspera/api/cos_node.rb +2 -2
- data/lib/aspera/api/faspex.rb +9 -7
- data/lib/aspera/api/httpgw.rb +37 -33
- data/lib/aspera/api/node.rb +38 -33
- data/lib/aspera/ascmd.rb +3 -1
- data/lib/aspera/ascp/installation.rb +62 -27
- data/lib/aspera/ascp/management.rb +1 -0
- data/lib/aspera/assert.rb +4 -0
- data/lib/aspera/cli/ascp_actions.rb +20 -41
- data/lib/aspera/cli/async_transfer_store.rb +2 -2
- data/lib/aspera/cli/bootstrapper.rb +11 -15
- data/lib/aspera/cli/command_line.rb +252 -0
- data/lib/aspera/cli/command_registry.rb +149 -33
- data/lib/aspera/cli/command_spec.rb +103 -14
- data/lib/aspera/cli/completion/ascli.bash +12 -0
- data/lib/aspera/cli/completion/ascli.fish +16 -0
- data/lib/aspera/cli/completion/ascli.zsh +19 -0
- data/lib/aspera/cli/context.rb +3 -0
- data/lib/aspera/cli/deprecation.rb +37 -0
- data/lib/aspera/cli/extended_value.rb +2 -0
- data/lib/aspera/cli/formatter.rb +87 -75
- data/lib/aspera/cli/gem_checker.rb +1 -1
- data/lib/aspera/cli/hints.rb +7 -6
- data/lib/aspera/cli/http.rb +21 -21
- data/lib/aspera/cli/info.rb +3 -0
- data/lib/aspera/cli/mcp_tool.rb +47 -83
- data/lib/aspera/cli/option_declarator.rb +33 -42
- data/lib/aspera/cli/option_registry.rb +69 -0
- data/lib/aspera/cli/option_types.rb +103 -0
- data/lib/aspera/cli/option_value.rb +281 -0
- data/lib/aspera/cli/options.schema.yaml +38 -5
- data/lib/aspera/cli/parser.rb +307 -848
- data/lib/aspera/cli/plugins/alee.rb +7 -4
- data/lib/aspera/cli/plugins/aoc.rb +435 -380
- data/lib/aspera/cli/plugins/ats.rb +58 -73
- data/lib/aspera/cli/plugins/base.rb +190 -240
- data/lib/aspera/cli/plugins/basic_auth.rb +2 -10
- data/lib/aspera/cli/plugins/config.rb +244 -178
- data/lib/aspera/cli/plugins/console.rb +102 -38
- data/lib/aspera/cli/plugins/cos.rb +6 -23
- data/lib/aspera/cli/plugins/factory.rb +3 -0
- data/lib/aspera/cli/plugins/faspex5.rb +176 -173
- data/lib/aspera/cli/plugins/faspio.rb +5 -10
- data/lib/aspera/cli/plugins/httpgw.rb +8 -11
- data/lib/aspera/cli/plugins/mcp.rb +20 -55
- data/lib/aspera/cli/plugins/node.rb +277 -311
- data/lib/aspera/cli/plugins/orchestrator.rb +90 -77
- data/lib/aspera/cli/plugins/preview.rb +79 -90
- data/lib/aspera/cli/plugins/server.rb +76 -50
- data/lib/aspera/cli/plugins/shares.rb +68 -116
- data/lib/aspera/cli/preset_actions.rb +17 -10
- data/lib/aspera/cli/preset_manager.rb +12 -2
- data/lib/aspera/cli/prompt.rb +35 -0
- data/lib/aspera/cli/result.rb +13 -18
- data/lib/aspera/cli/runner.rb +31 -54
- data/lib/aspera/cli/special_values.rb +5 -0
- data/lib/aspera/cli/sync_actions.rb +41 -37
- data/lib/aspera/cli/terminal_formatter.rb +9 -3
- data/lib/aspera/cli/transfer_actions.rb +0 -6
- data/lib/aspera/cli/transfer_agent.rb +29 -35
- data/lib/aspera/cli/vault_manager.rb +0 -17
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/cli/wizard.rb +4 -2
- data/lib/aspera/command_line_builder.rb +1 -0
- data/lib/aspera/coverage.rb +1 -0
- data/lib/aspera/environment.rb +7 -1
- data/lib/aspera/faspex_gw.rb +2 -1
- data/lib/aspera/faspex_postproc.rb +1 -0
- data/lib/aspera/graphql.rb +5 -5
- data/lib/aspera/json_rpc/client.rb +5 -5
- data/lib/aspera/keychain/encrypted_hash.rb +1 -1
- data/lib/aspera/keychain/one_password_api.rb +1 -1
- data/lib/aspera/link_header.rb +2 -2
- data/lib/aspera/log.rb +22 -25
- data/lib/aspera/markdown.rb +2 -0
- data/lib/aspera/mime.rb +25 -0
- data/lib/aspera/node_simulator.rb +1 -0
- data/lib/aspera/oauth/base.rb +35 -25
- data/lib/aspera/oauth/factory.rb +1 -0
- data/lib/aspera/oauth/generic.rb +1 -1
- data/lib/aspera/oauth/jwt.rb +1 -1
- data/lib/aspera/oauth/web.rb +9 -8
- data/lib/aspera/preview/file_types.rb +4 -4
- data/lib/aspera/preview/generator.rb +7 -0
- data/lib/aspera/preview/options.rb +4 -4
- data/lib/aspera/preview/terminal.rb +4 -3
- data/lib/aspera/preview/utils.rb +9 -6
- data/lib/aspera/products/connect.rb +1 -1
- data/lib/aspera/rainbow.rb +7 -0
- data/lib/aspera/rest/aspera_errors.rb +60 -0
- data/lib/aspera/rest/call_error.rb +27 -0
- data/lib/aspera/rest/client.rb +514 -0
- data/lib/aspera/rest/error_analyzer.rb +113 -0
- data/lib/aspera/rest/list.rb +143 -0
- data/lib/aspera/rest/parameters.rb +55 -0
- data/lib/aspera/rest/util.rb +176 -0
- data/lib/aspera/rest.rb +7 -621
- data/lib/aspera/schema/IBM Aspera Console-enhanced.yaml +1125 -0
- data/lib/aspera/schema/IBM Aspera on Cloud API-0.2.6-enhanced.yaml +39 -0
- data/lib/aspera/schema/IBM Aspera on Cloud Automation API-1.0.5-enhanced.yaml +2395 -0
- data/lib/aspera/schema/documentation.rb +13 -3
- data/lib/aspera/schema/registry.rb +18 -1
- data/lib/aspera/schema/validator.rb +92 -0
- data/lib/aspera/secret_hider.rb +36 -25
- data/lib/aspera/string_ext.rb +15 -0
- data/lib/aspera/temp_file_manager.rb +6 -5
- data/lib/aspera/transfer/parameters.rb +2 -0
- data/lib/aspera/transfer/spec.rb +1 -0
- data/lib/aspera/transfer/spec.schema.yaml +1 -0
- data/lib/aspera/uri_reader.rb +11 -11
- data/lib/aspera/web_auth/index.html +147 -0
- data/lib/aspera/web_auth/server.rb +81 -0
- data.tar.gz.sig +0 -0
- metadata +39 -7
- metadata.gz.sig +0 -0
- data/lib/aspera/colors.rb +0 -79
- data/lib/aspera/rest_call_error.rb +0 -25
- data/lib/aspera/rest_error_analyzer.rb +0 -111
- data/lib/aspera/rest_errors_aspera.rb +0 -58
- data/lib/aspera/rest_list.rb +0 -136
- data/lib/aspera/web_auth.rb +0 -211
|
@@ -9,6 +9,8 @@ require 'aspera/assert'
|
|
|
9
9
|
require 'aspera/yaml'
|
|
10
10
|
require 'digest'
|
|
11
11
|
require 'yaml'
|
|
12
|
+
require 'aspera/rainbow'
|
|
13
|
+
using Rainbow
|
|
12
14
|
|
|
13
15
|
module Aspera
|
|
14
16
|
module Cli
|
|
@@ -113,8 +115,8 @@ module Aspera
|
|
|
113
115
|
Log.log.error do
|
|
114
116
|
"Default config name [#{default_name}] specified for plugin [#{plugin_name_sym}], but it does not exist in config file.\n" \
|
|
115
117
|
"Please fix: either create preset:\n" \
|
|
116
|
-
"#{Info::CMD_NAME} config
|
|
117
|
-
"or remove default:\n#{Info::CMD_NAME} config
|
|
118
|
+
"#{Info::CMD_NAME} config preset initialize #{default_name} @json:'{}'\n" \
|
|
119
|
+
"or remove default:\n#{Info::CMD_NAME} config preset unset #{Key::DEFAULTS} #{plugin_name_sym}"
|
|
118
120
|
end
|
|
119
121
|
raise Cli::Error, "No such preset: #{default_name}"
|
|
120
122
|
end
|
|
@@ -192,10 +194,18 @@ module Aspera
|
|
|
192
194
|
if result.nil?
|
|
193
195
|
result = Key::GLOBAL
|
|
194
196
|
set_key(Key::DEFAULTS, CONF_GLOBAL_SYM, result)
|
|
197
|
+
# A default declaration requires the preset to exist
|
|
198
|
+
@config_presets[result] ||= {}
|
|
195
199
|
end
|
|
196
200
|
result
|
|
197
201
|
end
|
|
198
202
|
|
|
203
|
+
# @return [Boolean] true if `preset_name` is declared as the global default preset (does not create it)
|
|
204
|
+
def global_preset?(preset_name)
|
|
205
|
+
defaults = @config_presets[Key::DEFAULTS]
|
|
206
|
+
defaults.is_a?(Hash) && defaults[CONF_GLOBAL_SYM.to_s].eql?(preset_name)
|
|
207
|
+
end
|
|
208
|
+
|
|
199
209
|
# Set param in the global defaults preset
|
|
200
210
|
def set_global_default(key, value)
|
|
201
211
|
set_key(global_default_preset, key, value)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'io/console'
|
|
4
|
+
require 'aspera/assert'
|
|
5
|
+
|
|
6
|
+
module Aspera
|
|
7
|
+
module Cli
|
|
8
|
+
# Console input
|
|
9
|
+
module Prompt
|
|
10
|
+
# Prompt user for console input
|
|
11
|
+
# @param prompt [String] prompt string to display
|
|
12
|
+
# @param sensitive [Boolean] whether to hide typed input
|
|
13
|
+
# @return [String] user input stripped of trailing newline
|
|
14
|
+
def prompt_user_input(prompt, sensitive: false)
|
|
15
|
+
return $stdin.getpass("#{prompt}> ") if sensitive
|
|
16
|
+
print("#{prompt}> ")
|
|
17
|
+
line = $stdin.gets
|
|
18
|
+
Aspera.assert_type(line, String) { 'Unexpected end of standard input' }
|
|
19
|
+
line.chomp
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Prompt user for input in a list of symbols
|
|
23
|
+
# @param prompt [String] prompt to display
|
|
24
|
+
# @param sym_list [Array] list of symbols to select from
|
|
25
|
+
# @return [Symbol] selected symbol
|
|
26
|
+
def prompt_user_input_in_list(prompt, sym_list)
|
|
27
|
+
loop do
|
|
28
|
+
input = prompt_user_input(prompt).to_sym
|
|
29
|
+
return input if sym_list.any? { |a| a.eql?(input) }
|
|
30
|
+
$stderr.puts("No such #{prompt}: #{input}, select one of: #{sym_list.join(', ')}") # rubocop:disable Style/StderrPuts
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
data/lib/aspera/cli/result.rb
CHANGED
|
@@ -20,9 +20,11 @@ module Aspera
|
|
|
20
20
|
class Result
|
|
21
21
|
attr_reader :data, :fields
|
|
22
22
|
|
|
23
|
-
# @param data [Object
|
|
24
|
-
# @param fields [
|
|
23
|
+
# @param data [nil, Object] The result data
|
|
24
|
+
# @param fields [nil, Array<String>, Proc] Specification of fields to include
|
|
25
25
|
def initialize(data: nil, fields: nil)
|
|
26
|
+
Aspera.assert_type(fields, NilClass, Array, Proc)
|
|
27
|
+
Aspera.assert_array_all(fields, String) if fields.is_a?(Array)
|
|
26
28
|
@data = data
|
|
27
29
|
@fields = fields
|
|
28
30
|
end
|
|
@@ -39,18 +41,12 @@ module Aspera
|
|
|
39
41
|
# Apply field filtering once for formats that need it
|
|
40
42
|
filtered_data = formatter.filter_list_on_fields(@data)
|
|
41
43
|
case formatter.format_type
|
|
42
|
-
when :
|
|
43
|
-
|
|
44
|
-
when :
|
|
45
|
-
|
|
46
|
-
when :
|
|
47
|
-
|
|
48
|
-
when :json
|
|
49
|
-
formatter.display_message(:data, JSON.generate(filtered_data))
|
|
50
|
-
when :jsonpp
|
|
51
|
-
formatter.display_message(:data, JSON.pretty_generate(filtered_data))
|
|
52
|
-
when :yaml
|
|
53
|
-
formatter.display_message(:data, YAML.dump(filtered_data))
|
|
44
|
+
when :nagios then Nagios.process(@data)
|
|
45
|
+
when :text then formatter.display_message(:data, @data.to_s)
|
|
46
|
+
when :ruby then formatter.display_message(:data, PP.pp(filtered_data, +''))
|
|
47
|
+
when :json then formatter.display_message(:data, JSON.generate(filtered_data))
|
|
48
|
+
when :jsonpp then formatter.display_message(:data, JSON.pretty_generate(filtered_data))
|
|
49
|
+
when :yaml then formatter.display_message(:data, YAML.dump(filtered_data))
|
|
54
50
|
when :image
|
|
55
51
|
if @data.nil?
|
|
56
52
|
formatter.display_message(:data, formatter.special_format('null (no image)'))
|
|
@@ -82,8 +78,7 @@ module Aspera
|
|
|
82
78
|
# here, data is the image blob
|
|
83
79
|
formatter.display_message(:data, Preview::Terminal.build(data, **formatter.image_options))
|
|
84
80
|
end
|
|
85
|
-
else
|
|
86
|
-
Aspera.error_unexpected_value(formatter.format_type) { 'format' }
|
|
81
|
+
else Aspera.error_unexpected_value(formatter.format_type) { 'format' }
|
|
87
82
|
end
|
|
88
83
|
end
|
|
89
84
|
end
|
|
@@ -228,7 +223,7 @@ module Aspera
|
|
|
228
223
|
end
|
|
229
224
|
|
|
230
225
|
# Object list result (Array of Hash)
|
|
231
|
-
# @note The
|
|
226
|
+
# @note The `total` parameter is used to display pagination information (e.g., "Items: 10/100")
|
|
232
227
|
class ObjectList < Result
|
|
233
228
|
attr_reader :total
|
|
234
229
|
|
|
@@ -299,7 +294,7 @@ module Aspera
|
|
|
299
294
|
# @param id_result [String] Key in the result Hash used as item identifier
|
|
300
295
|
# @param fields [Object] Fields hint passed to the Result constructor (non-bulk only, when not :default)
|
|
301
296
|
# @param bfail [Boolean] When true, re-raise errors; when false, capture them as a status string
|
|
302
|
-
# @yieldparam item [Object] Each item in
|
|
297
|
+
# @yieldparam item [Object] Each item in `items`
|
|
303
298
|
# @yieldreturn [Hash, Array, nil] REST response; a Hash replaces the default id-keyed result
|
|
304
299
|
# @return [Result::ObjectList, Result::SingleObject]
|
|
305
300
|
def bulk(items, is_bulk:, command:, id_result: 'id', fields: :default, bfail: true)
|
data/lib/aspera/cli/runner.rb
CHANGED
|
@@ -166,7 +166,7 @@ module Aspera
|
|
|
166
166
|
rescue Cli::SchemaRequest => e; exception_info = {e: e, t: 'Schema'}
|
|
167
167
|
rescue Cli::Error => e; exception_info = {e: e, t: 'Tool', usage: true}
|
|
168
168
|
rescue Transfer::Error => e; exception_info = {e: e, t: 'Transfer'}
|
|
169
|
-
rescue
|
|
169
|
+
rescue Rest::CallError => e; exception_info = {e: e, t: 'Rest'}
|
|
170
170
|
rescue SocketError => e; exception_info = {e: e, t: 'Network'}
|
|
171
171
|
rescue StandardError => e; exception_info = {e: e, t: "Other(#{e.class.name})", debug: true}
|
|
172
172
|
rescue Interrupt => e; exception_info = {e: e, t: 'Interruption', debug: true}
|
|
@@ -228,29 +228,19 @@ module Aspera
|
|
|
228
228
|
Result::Text.new(usage_text(plugin: plugin))
|
|
229
229
|
end
|
|
230
230
|
|
|
231
|
-
#
|
|
232
|
-
#
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
case k.to_sym
|
|
243
|
-
when :level then Log.instance.level = v.to_sym
|
|
244
|
-
when :type then Log.instance.logger_type = v.to_sym
|
|
245
|
-
when :format then Log.instance.formatter = v
|
|
246
|
-
when :secrets then SecretHider.instance.log_secrets = BoolValue.true?(v)
|
|
247
|
-
else Aspera.error_unexpected_value(k) { 'log sub-option (level, type, format, secrets)' }
|
|
248
|
-
end
|
|
231
|
+
# `on_set` callback of the `log` option (dot-notation sub-properties): `level`, `type`, `format`, `secrets`
|
|
232
|
+
# @param value [Hash] sub-properties to set
|
|
233
|
+
def option_log=(value)
|
|
234
|
+
Aspera.assert_type(value, Hash)
|
|
235
|
+
value.each do |k, v|
|
|
236
|
+
case k.to_sym
|
|
237
|
+
when :level then Log.instance.level = v.to_sym
|
|
238
|
+
when :type then Log.instance.logger_type = v.to_sym
|
|
239
|
+
when :format then Log.instance.formatter = v
|
|
240
|
+
when :secrets then SecretHider.instance.log_secrets = Parser.get_from_list(v.to_s, 'log secrets', BoolValue::ALL)
|
|
241
|
+
else Aspera.error_unexpected_value(k) { 'log sub-option (level, type, format, secrets)' }
|
|
249
242
|
end
|
|
250
|
-
when :get
|
|
251
|
-
return {level: Log.instance.level, type: Log.instance.logger_type, format: Log.instance.formatter, secrets: SecretHider.instance.log_secrets}
|
|
252
243
|
end
|
|
253
|
-
nil
|
|
254
244
|
end
|
|
255
245
|
|
|
256
246
|
private
|
|
@@ -279,25 +269,11 @@ module Aspera
|
|
|
279
269
|
registry = plugin.class.command_registry
|
|
280
270
|
cmds = registry.children_of(path)
|
|
281
271
|
label = plugin.class.name.split('::').last.downcase
|
|
282
|
-
# Build label with positional argument slots inserted after each
|
|
272
|
+
# Build label with positional argument slots inserted after each command
|
|
283
273
|
# e.g. [:access_keys, :do, :download] -> "node access_keys do <access_key_id> download"
|
|
284
|
-
path.
|
|
285
|
-
label += " #{
|
|
286
|
-
|
|
287
|
-
seg_spec = registry[seg_path]
|
|
288
|
-
if seg_spec && registry.children_of(seg_path).any? && seg_spec.arguments
|
|
289
|
-
seg_spec.arguments.each do |arg_spec|
|
|
290
|
-
label += " <#{arg_spec.name}>"
|
|
291
|
-
end
|
|
292
|
-
end
|
|
293
|
-
end
|
|
294
|
-
if cmds.none?
|
|
295
|
-
leaf_spec = registry[path]
|
|
296
|
-
leaf_spec&.arguments&.each do |arg_spec|
|
|
297
|
-
argument = arg_spec.mandatory ? "<#{arg_spec.name}>" : "[#{arg_spec.name}]"
|
|
298
|
-
argument += '...' if arg_spec.multiple
|
|
299
|
-
label += " #{argument}"
|
|
300
|
-
end
|
|
274
|
+
path.each_index do |i|
|
|
275
|
+
label += " #{path[i]}"
|
|
276
|
+
registry.arguments_at(path[0..i]).each { |arg_spec| label += " #{arg_spec.syntax}" }
|
|
301
277
|
end
|
|
302
278
|
if cmds.any?
|
|
303
279
|
# Intermediate node: show current command + description, then list subcommands
|
|
@@ -314,7 +290,7 @@ module Aspera
|
|
|
314
290
|
spec = registry[path]
|
|
315
291
|
lines << "\nCOMMAND: #{label}"
|
|
316
292
|
lines << " #{spec.description}" if spec&.description
|
|
317
|
-
display_args =
|
|
293
|
+
display_args = registry.arguments_at(path)
|
|
318
294
|
# transfer_paths commands use --sources for the file list; default is positional args (@args)
|
|
319
295
|
if spec&.transfer_paths
|
|
320
296
|
file_desc = if spec.transfer_paths == :receive
|
|
@@ -395,7 +371,7 @@ module Aspera
|
|
|
395
371
|
@context.config = Plugins::Config.new(context: @context)
|
|
396
372
|
@context.man_header = true
|
|
397
373
|
# Sync cache_tokens from Config into the OAuth persist_mgr (now that option is parsed)
|
|
398
|
-
OAuth::Factory.instance.persist_mgr = @context.persistency if @context.
|
|
374
|
+
OAuth::Factory.instance.persist_mgr = @context.persistency if @context.options.get_option(:cache_tokens)
|
|
399
375
|
# Email service: depends on options declared by Config
|
|
400
376
|
@context.mailer = Mailer.new(@context.options, @context.main_folder)
|
|
401
377
|
# Secret finder: depends on options (:secret) and presets, both set by Bootstrapper
|
|
@@ -450,32 +426,33 @@ module Aspera
|
|
|
450
426
|
Log.log.debug('declare_global_options')
|
|
451
427
|
@context.options.declare(:help, description: 'Show this message', allowed: Type::NONE, short: 'h') do
|
|
452
428
|
@option_help = true
|
|
453
|
-
@context.
|
|
429
|
+
@context.help_requested = true
|
|
454
430
|
end
|
|
455
431
|
@context.options.declare(:show_config, description: 'Display parameters used for the provided action', allowed: Type::NONE) { @option_show_config = true }
|
|
456
432
|
@context.options.declare(:version, description: 'Display version', allowed: Type::NONE, short: 'v') { @context.formatter.display_message(:data, Cli::VERSION); Process.exit(0) } # rubocop:disable Style/Semicolon
|
|
457
433
|
@context.options.declare(
|
|
458
434
|
:ui, description: 'Method to start browser',
|
|
459
|
-
allowed: USER_INTERFACES,
|
|
460
|
-
|
|
435
|
+
allowed: USER_INTERFACES, default: Environment.instance.url_method,
|
|
436
|
+
on_set: Environment.instance.method(:url_method=)
|
|
461
437
|
)
|
|
462
438
|
@context.options.declare(
|
|
463
439
|
:invalid_characters, description: 'Replacement character and invalid filename characters',
|
|
464
|
-
|
|
440
|
+
default: Environment.instance.file_illegal_characters,
|
|
441
|
+
on_set: Environment.instance.method(:file_illegal_characters=)
|
|
465
442
|
)
|
|
466
|
-
@context.options.declare(:log_level, description: 'Log level', allowed: Log::LEVELS,
|
|
467
|
-
@context.options.declare(:log_format, description: 'Log formatter', allowed: [Proc, Logger::Formatter, String],
|
|
468
|
-
@context.options.declare(:logger, description: 'Logging method', allowed: Log::LOG_TYPES,
|
|
469
|
-
@context.options.declare(:log, description: 'Logging options (dot-notation: level, type, format, secrets)',
|
|
443
|
+
@context.options.declare(:log_level, description: 'Log level', allowed: Log::LEVELS, default: Log.instance.level, on_set: Log.instance.method(:level=))
|
|
444
|
+
@context.options.declare(:log_format, description: 'Log formatter', allowed: [Proc, Logger::Formatter, String], on_set: Log.instance.method(:formatter=))
|
|
445
|
+
@context.options.declare(:logger, description: 'Logging method', allowed: Log::LOG_TYPES, on_set: Log.instance.method(:logger_type=))
|
|
446
|
+
@context.options.declare(:log, description: 'Logging options (dot-notation: level, type, format, secrets)', on_set: method(:option_log=), schema: Schema::Registry::LOG_OPTIONS)
|
|
470
447
|
@context.options.declare(:lock_port, description: 'Prevent dual execution of a command, e.g. in cron', allowed: Type::INTEGER)
|
|
471
448
|
@context.options.declare(:once_only, description: 'Process only new items (some commands)', allowed: Type::BOOLEAN, default: false)
|
|
472
|
-
@context.options.declare(:log_secrets, description: 'Show passwords in logs', allowed: Type::BOOLEAN,
|
|
473
|
-
@context.options.declare(:clean_temp, description: 'Cleanup temporary files on exit', allowed: Type::BOOLEAN,
|
|
474
|
-
@context.options.declare(:temp_folder, description: 'Temporary folder',
|
|
449
|
+
@context.options.declare(:log_secrets, description: 'Show passwords in logs', allowed: Type::BOOLEAN, default: SecretHider.instance.log_secrets, on_set: SecretHider.instance.method(:log_secrets=))
|
|
450
|
+
@context.options.declare(:clean_temp, description: 'Cleanup temporary files on exit', allowed: Type::BOOLEAN, default: TempFileManager.instance.cleanup_on_exit, on_set: TempFileManager.instance.method(:cleanup_on_exit=))
|
|
451
|
+
@context.options.declare(:temp_folder, description: 'Temporary folder', default: TempFileManager.instance.global_temp, on_set: TempFileManager.instance.method(:global_temp=))
|
|
475
452
|
@context.options.declare(:pid_file, description: 'Write process identifier to file, delete on exit')
|
|
476
453
|
@context.options.declare(
|
|
477
454
|
:parser, description: 'Default parser for structured parameters and options',
|
|
478
|
-
|
|
455
|
+
on_set: ExtendedValue.instance.method(:default_decoder=),
|
|
479
456
|
allowed: ExtendedValue::DEFAULT_DECODERS,
|
|
480
457
|
default: ExtendedValue::DEFAULT_DECODERS.first
|
|
481
458
|
)
|
|
@@ -5,10 +5,15 @@ module Aspera
|
|
|
5
5
|
# base class for plugins modules
|
|
6
6
|
module SpecialValues
|
|
7
7
|
# special values
|
|
8
|
+
# `INIT` to unitialize with all current IDs
|
|
8
9
|
INIT = 'INIT'
|
|
10
|
+
# `ALL` means "all ids"
|
|
9
11
|
ALL = 'ALL'
|
|
12
|
+
# `DEF` for the default list
|
|
10
13
|
DEF = 'DEF'
|
|
14
|
+
# `END` of arguments
|
|
11
15
|
EOA = 'END'
|
|
16
|
+
# `LATEST`
|
|
12
17
|
LATEST = 'LATEST'
|
|
13
18
|
end
|
|
14
19
|
end
|
|
@@ -16,7 +16,7 @@ module Aspera
|
|
|
16
16
|
%w[Syncd Error Confl Pconf] +
|
|
17
17
|
(23..24).map { |i| "P(#{i})" }).freeze
|
|
18
18
|
# Positional arguments shared by sync transfer commands (push/pull/bidi) and sync admin commands.
|
|
19
|
-
PATH_AND_INFO_ARGS = [{name: :path
|
|
19
|
+
PATH_AND_INFO_ARGS = [{name: :path}, {name: :sync_info, type: Hash, mandatory: false, default: {}}].freeze
|
|
20
20
|
# Names of the leaf commands registered under any `sync admin` node.
|
|
21
21
|
ADMIN_COMMANDS = %i[find status meta counters file_info overview query].freeze
|
|
22
22
|
# When a plugin class includes SyncActions, register the :sql option
|
|
@@ -32,13 +32,46 @@ module Aspera
|
|
|
32
32
|
# @param admin_path [Symbol, Array<Symbol>] path relative to current scope, e.g. :admin
|
|
33
33
|
def register_sync_admin_commands(base, admin_path)
|
|
34
34
|
base.commands_under(admin_path) do
|
|
35
|
-
base.command(
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
35
|
+
base.command(
|
|
36
|
+
:find, description: 'Find sync database files', arguments: [{name: :path}],
|
|
37
|
+
action: lambda do |path:, **|
|
|
38
|
+
dbs = Sync::Operations.list_db_files(path)
|
|
39
|
+
Result::ObjectList.new(dbs.keys.map { |n| {name: n, path: dbs[n]} })
|
|
40
|
+
end
|
|
41
|
+
)
|
|
42
|
+
base.command(
|
|
43
|
+
:status, description: 'Show sync session status', arguments: PATH_AND_INFO_ARGS,
|
|
44
|
+
action: ->(path:, sync_info: {}, **) { Result::SingleObject.new(Sync::Operations.admin_status(async_info_from_args(path: path, sync_info: sync_info))) }
|
|
45
|
+
)
|
|
46
|
+
base.command(
|
|
47
|
+
:meta, description: 'Show sync session metadata', arguments: PATH_AND_INFO_ARGS,
|
|
48
|
+
action: lambda do |path:, sync_info: {}, **|
|
|
49
|
+
require 'aspera/sync/database'
|
|
50
|
+
Result::SingleObject.new(db_from_args(path: path, sync_info: sync_info).meta(options.get_option(:sql)))
|
|
51
|
+
end
|
|
52
|
+
)
|
|
53
|
+
base.command(
|
|
54
|
+
:counters, description: 'Show sync counters', arguments: PATH_AND_INFO_ARGS,
|
|
55
|
+
action: lambda do |path:, sync_info: {}, **|
|
|
56
|
+
require 'aspera/sync/database'
|
|
57
|
+
Result::SingleObject.new(db_from_args(path: path, sync_info: sync_info).counters(options.get_option(:sql)))
|
|
58
|
+
end
|
|
59
|
+
)
|
|
60
|
+
base.command(:file_info, description: 'Show per-file sync state', arguments: PATH_AND_INFO_ARGS, action: :action_sync_admin_file_info)
|
|
61
|
+
base.command(
|
|
62
|
+
:overview, description: 'Show sync database overview', arguments: PATH_AND_INFO_ARGS,
|
|
63
|
+
action: lambda do |path:, sync_info: {}, **|
|
|
64
|
+
require 'aspera/sync/database'
|
|
65
|
+
Result::ObjectList.new(db_from_args(path: path, sync_info: sync_info).overview, fields: %w[table name type])
|
|
66
|
+
end
|
|
67
|
+
)
|
|
68
|
+
base.command(
|
|
69
|
+
:query, description: 'Execute a raw SQL query', arguments: PATH_AND_INFO_ARGS,
|
|
70
|
+
action: lambda do |path:, sync_info: {}, **|
|
|
71
|
+
require 'aspera/sync/database'
|
|
72
|
+
Result.auto(db_from_args(path: path, sync_info: sync_info).execute(options.get_option(:sql, mandatory: true)))
|
|
73
|
+
end
|
|
74
|
+
)
|
|
42
75
|
end
|
|
43
76
|
end
|
|
44
77
|
end
|
|
@@ -119,25 +152,6 @@ module Aspera
|
|
|
119
152
|
Sync::Database.new(Sync::Operations.session_db_file(sync_info))
|
|
120
153
|
end
|
|
121
154
|
|
|
122
|
-
def action_sync_admin_status(path:, sync_info: {}, **)
|
|
123
|
-
Result::SingleObject.new(Sync::Operations.admin_status(async_info_from_args(path: path, sync_info: sync_info)))
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
def action_sync_admin_find(path:, **)
|
|
127
|
-
dbs = Sync::Operations.list_db_files(path)
|
|
128
|
-
Result::ObjectList.new(dbs.keys.map { |n| {name: n, path: dbs[n]} })
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
def action_sync_admin_meta(path:, sync_info: {}, **)
|
|
132
|
-
require 'aspera/sync/database'
|
|
133
|
-
Result::SingleObject.new(db_from_args(path: path, sync_info: sync_info).meta(options.get_option(:sql)))
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
def action_sync_admin_counters(path:, sync_info: {}, **)
|
|
137
|
-
require 'aspera/sync/database'
|
|
138
|
-
Result::SingleObject.new(db_from_args(path: path, sync_info: sync_info).counters(options.get_option(:sql)))
|
|
139
|
-
end
|
|
140
|
-
|
|
141
155
|
def action_sync_admin_file_info(path:, sync_info: {}, **)
|
|
142
156
|
require 'aspera/sync/database'
|
|
143
157
|
result = db_from_args(path: path, sync_info: sync_info).file_info(options.get_option(:sql))
|
|
@@ -147,16 +161,6 @@ module Aspera
|
|
|
147
161
|
Result::ObjectList.new(result, fields: %w[sstate record_id f_meta_path message])
|
|
148
162
|
end
|
|
149
163
|
|
|
150
|
-
def action_sync_admin_overview(path:, sync_info: {}, **)
|
|
151
|
-
require 'aspera/sync/database'
|
|
152
|
-
Result::ObjectList.new(db_from_args(path: path, sync_info: sync_info).overview, fields: %w[table name type])
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
def action_sync_admin_query(path:, sync_info: {}, **)
|
|
156
|
-
require 'aspera/sync/database'
|
|
157
|
-
Result.auto(db_from_args(path: path, sync_info: sync_info).execute(options.get_option(:sql, mandatory: true)))
|
|
158
|
-
end
|
|
159
|
-
|
|
160
164
|
# Execute a sync transfer for a given direction.
|
|
161
165
|
# @param direction [Symbol] one of Sync::Operations::DIRECTIONS (:push, :pull, :bidi)
|
|
162
166
|
# @param path [String] local or remote path (depends on direction)
|
|
@@ -4,6 +4,8 @@ require 'aspera/environment'
|
|
|
4
4
|
require 'aspera/formatter_interface'
|
|
5
5
|
require 'aspera/markdown'
|
|
6
6
|
require 'aspera/assert'
|
|
7
|
+
require 'aspera/rainbow'
|
|
8
|
+
using Rainbow
|
|
7
9
|
|
|
8
10
|
module Aspera
|
|
9
11
|
module Cli
|
|
@@ -11,9 +13,13 @@ module Aspera
|
|
|
11
13
|
# @see FormatterInterface
|
|
12
14
|
# @see MarkdownFormatter (in build/lib/doc_helper.rb)
|
|
13
15
|
module TerminalFormatter
|
|
14
|
-
HINT = 'HINT:'.bg_green.gray.blink.freeze
|
|
15
16
|
include FormatterInterface
|
|
16
17
|
|
|
18
|
+
# Prefix for hint messages (evaluated on each call, as colors can be enabled or disabled by option)
|
|
19
|
+
def hint
|
|
20
|
+
'HINT:'.bg(:green).white.blink
|
|
21
|
+
end
|
|
22
|
+
|
|
17
23
|
# Format boolean with colored symbol (+/- or Y/ )
|
|
18
24
|
def tick(yes)
|
|
19
25
|
result =
|
|
@@ -29,7 +35,7 @@ module Aspera
|
|
|
29
35
|
# Format special values with colors (dim for empty, reverse for others)
|
|
30
36
|
def special_format(what)
|
|
31
37
|
result = "<#{what}>"
|
|
32
|
-
return %w[null empty].any? { |s| what.include?(s) } ? result.
|
|
38
|
+
return %w[null empty].any? { |s| what.include?(s) } ? result.faint : result.inverse
|
|
33
39
|
end
|
|
34
40
|
|
|
35
41
|
# Prepare table row for terminal display (word wrap arrays)
|
|
@@ -59,7 +65,7 @@ module Aspera
|
|
|
59
65
|
end
|
|
60
66
|
end
|
|
61
67
|
|
|
62
|
-
module_function :tick, :special_format, :check_row, :markdown_text
|
|
68
|
+
module_function :hint, :tick, :special_format, :check_row, :markdown_text
|
|
63
69
|
end
|
|
64
70
|
end
|
|
65
71
|
end
|
|
@@ -36,12 +36,6 @@ module Aspera
|
|
|
36
36
|
Result::SingleObject.new(entry)
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
-
# List all persisted async transfer entries.
|
|
40
|
-
def action_transfer_list(**)
|
|
41
|
-
rows = async_transfer_store.list
|
|
42
|
-
Result::ObjectList.new(rows, fields: %w[job_id agent_type status started_at ended_at bytes_transferred transfer_id])
|
|
43
|
-
end
|
|
44
|
-
|
|
45
39
|
TERMINAL_STATUSES = %w[completed failed cancelled].freeze
|
|
46
40
|
private_constant :TERMINAL_STATUSES
|
|
47
41
|
|
|
@@ -32,9 +32,15 @@ module Aspera
|
|
|
32
32
|
<%=ts.to_yaml%>
|
|
33
33
|
END_OF_TEMPLATE
|
|
34
34
|
CP4I_REMOTE_HOST_LB = 'N/A'
|
|
35
|
+
# Transfer spec parameters overridden by option `ts`
|
|
36
|
+
TS_DEFAULTS = {
|
|
37
|
+
'create_dir' => true,
|
|
38
|
+
'resume_policy' => 'sparse_csum'
|
|
39
|
+
}.freeze
|
|
35
40
|
private_constant :FILE_LIST_FROM_TRANSFER_SPEC,
|
|
36
41
|
:FILE_LIST_OPTIONS,
|
|
37
|
-
:DEFAULT_TRANSFER_NOTIFY_TEMPLATE
|
|
42
|
+
:DEFAULT_TRANSFER_NOTIFY_TEMPLATE,
|
|
43
|
+
:TS_DEFAULTS
|
|
38
44
|
|
|
39
45
|
extend OptionDeclarator
|
|
40
46
|
|
|
@@ -42,8 +48,8 @@ module Aspera
|
|
|
42
48
|
option :to_folder, description: 'Destination folder for transferred files'
|
|
43
49
|
option :sources, description: "How list of transferred files is provided (#{FILE_LIST_OPTIONS.join(',')})", default: FILE_LIST_FROM_ARGS
|
|
44
50
|
option :src_type, description: 'Type of file list', allowed: %i[list pair], default: :list
|
|
45
|
-
option :transfer, description: 'Transfer agent type, or agent parameters with optional agent key', allowed: [Hash, String], schema: Schema::Registry::TRANSFER_AGENT_OPTIONS
|
|
46
|
-
option :transfer_info, description: 'Parameters for transfer agent', deprecation: 'use --transfer instead', schema: Schema::Registry::TRANSFER_AGENT_OPTIONS
|
|
51
|
+
option :transfer, description: 'Transfer agent type, or agent parameters with optional agent key', allowed: [Hash, String], shorthand: 'agent', schema: Schema::Registry::TRANSFER_AGENT_OPTIONS
|
|
52
|
+
option :transfer_info, description: 'Parameters for transfer agent', deprecation: {last: '4.26.2', message: 'use --transfer instead'}, schema: Schema::Registry::TRANSFER_AGENT_OPTIONS
|
|
47
53
|
|
|
48
54
|
# @param context [Context] Application context
|
|
49
55
|
def initialize(context)
|
|
@@ -51,12 +57,7 @@ module Aspera
|
|
|
51
57
|
Aspera.assert_type(context.options, Parser) { 'context.options' }
|
|
52
58
|
@context = context
|
|
53
59
|
# Command line can override transfer spec
|
|
54
|
-
@user_transfer_spec =
|
|
55
|
-
'create_dir' => true,
|
|
56
|
-
'resume_policy' => 'sparse_csum'
|
|
57
|
-
}
|
|
58
|
-
# options for transfer agent (agent type + agent-specific parameters)
|
|
59
|
-
@transfer_options = {}
|
|
60
|
+
@user_transfer_spec = TS_DEFAULTS.dup
|
|
60
61
|
# the currently selected transfer agent
|
|
61
62
|
@agent = nil
|
|
62
63
|
# source/destination pair, like "paths" of transfer spec
|
|
@@ -64,9 +65,7 @@ module Aspera
|
|
|
64
65
|
# HTTPGW URL provided by webapp
|
|
65
66
|
@httpgw_url_lambda = nil
|
|
66
67
|
self.class.declare_options(@context.options)
|
|
67
|
-
@context.options.
|
|
68
|
-
@context.options.set_handler(:transfer, object: self, method: :option_transfer)
|
|
69
|
-
@context.options.set_handler(:transfer_info, object: self, method: :transfer_options)
|
|
68
|
+
@context.options.on_set(:ts, method(:user_transfer_spec=))
|
|
70
69
|
@context.options.parse_options!
|
|
71
70
|
@notification_cb = nil
|
|
72
71
|
if !@context.options.get_option(:notify_to).nil?
|
|
@@ -80,22 +79,17 @@ module Aspera
|
|
|
80
79
|
end
|
|
81
80
|
end
|
|
82
81
|
|
|
83
|
-
|
|
82
|
+
attr_reader :user_transfer_spec
|
|
84
83
|
|
|
85
|
-
#
|
|
86
|
-
#
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
@transfer_options = @transfer_options.deep_merge(value)
|
|
95
|
-
when :get
|
|
96
|
-
return @transfer_options
|
|
97
|
-
end
|
|
98
|
-
nil
|
|
84
|
+
# `on_set` callback of option `ts`: values override defaults
|
|
85
|
+
# @param value [Hash] transfer spec parameters
|
|
86
|
+
def user_transfer_spec=(value)
|
|
87
|
+
@user_transfer_spec = TS_DEFAULTS.merge(value)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# @return [Hash] agent type (key `agent`) and agent parameters, from options `transfer` and deprecated `transfer_info`
|
|
91
|
+
def transfer_options
|
|
92
|
+
@context.options.get_option(:transfer_info).deep_merge(@context.options.get_option(:transfer))
|
|
99
93
|
end
|
|
100
94
|
|
|
101
95
|
def agent_instance=(instance)
|
|
@@ -106,10 +100,10 @@ module Aspera
|
|
|
106
100
|
def agent_instance
|
|
107
101
|
return @agent unless @agent.nil?
|
|
108
102
|
# agent type: from composite option 'agent' key, default :direct
|
|
109
|
-
raw_type =
|
|
103
|
+
raw_type = transfer_options['agent'] || :direct
|
|
110
104
|
agent_type = Parser.get_from_list(raw_type.to_s, 'transfer agent', Agent::Factory::ALL.keys)
|
|
111
105
|
# set keys as symbols, strip internal keys not forwarded to the agent constructor
|
|
112
|
-
agent_options =
|
|
106
|
+
agent_options = transfer_options.except('agent', 'asynchronous').symbolize_keys
|
|
113
107
|
agent_options[:progress] = @context.progress_bar
|
|
114
108
|
agent_options[:config_dir] = @context.main_folder
|
|
115
109
|
# special cases
|
|
@@ -210,8 +204,8 @@ module Aspera
|
|
|
210
204
|
when FILE_LIST_FROM_TRANSFER_SPEC
|
|
211
205
|
Log.log.debug('assume list provided in transfer spec')
|
|
212
206
|
special_case_direct_with_list =
|
|
213
|
-
(
|
|
214
|
-
Transfer::Parameters.ascp_args_file_list?(
|
|
207
|
+
(transfer_options['agent'] || :direct).to_sym.eql?(:direct) &&
|
|
208
|
+
Transfer::Parameters.ascp_args_file_list?(transfer_options['ascp_args'])
|
|
215
209
|
Aspera.assert(!@transfer_paths.nil? || special_case_direct_with_list, type: Cli::BadArgument) { 'transfer spec on command line must have sources' }
|
|
216
210
|
# can be nil
|
|
217
211
|
@transfer_paths
|
|
@@ -227,7 +221,7 @@ module Aspera
|
|
|
227
221
|
|
|
228
222
|
# Start a transfer and wait for completion, plugins shall use this method
|
|
229
223
|
# @param transfer_spec [Hash] transfer specification
|
|
230
|
-
# @param rest_token [Rest, nil] if oauth token regeneration supported
|
|
224
|
+
# @param rest_token [Rest::Client, nil] if oauth token regeneration supported
|
|
231
225
|
def start(transfer_spec, rest_token: nil)
|
|
232
226
|
# check parameters
|
|
233
227
|
Aspera.assert_type(transfer_spec, Hash) { 'transfer_spec' }
|
|
@@ -264,13 +258,13 @@ module Aspera
|
|
|
264
258
|
# create transfer agent
|
|
265
259
|
agent_instance.start_transfer(transfer_spec, token_regenerator: rest_token)
|
|
266
260
|
# --- async mode ---
|
|
267
|
-
if
|
|
268
|
-
agent_type = (
|
|
261
|
+
if transfer_options['asynchronous']
|
|
262
|
+
agent_type = (transfer_options['agent'] || 'direct').to_s
|
|
269
263
|
# In-process agents (direct, httpgw) expose last_job_id directly.
|
|
270
264
|
# Remote-daemon agents do not override last_job_id (returns nil): generate a UUID.
|
|
271
265
|
job_id = agent_instance.last_job_id || SecureRandom.uuid
|
|
272
266
|
# Base agent_params from transfer options (strip internal keys)
|
|
273
|
-
agent_params =
|
|
267
|
+
agent_params = transfer_options.except('agent', 'asynchronous')
|
|
274
268
|
# For daemon agents: capture any runtime-resolved connection detail
|
|
275
269
|
case agent_type
|
|
276
270
|
when 'desktop'
|
|
@@ -10,17 +10,6 @@ module Aspera
|
|
|
10
10
|
# Mixin providing vault/keychain functionality to Plugin::Config.
|
|
11
11
|
# Depends on `options` and `context.main_folder` being available in the including class.
|
|
12
12
|
module VaultManager
|
|
13
|
-
def action_vault_show(label:, id: nil, **)
|
|
14
|
-
v = vault_required
|
|
15
|
-
kwargs = id && v.method(:get).parameters.any? { |_t, n| n == :id } ? {id: id} : {}
|
|
16
|
-
Result::SingleObject.new(v.get(label: label, **kwargs))
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def action_vault_create(info:, **)
|
|
20
|
-
vault_required.set(info.symbolize_keys)
|
|
21
|
-
Result::Status.new('Secret added')
|
|
22
|
-
end
|
|
23
|
-
|
|
24
13
|
# Import secrets from a JSON array; skips entries missing :label
|
|
25
14
|
def action_vault_import(secrets:, **)
|
|
26
15
|
bulk_result(secrets, command: :import, id_result: 'label') do |entry|
|
|
@@ -36,12 +25,6 @@ module Aspera
|
|
|
36
25
|
Result::Status.new("Secret deleted: #{label}")
|
|
37
26
|
end
|
|
38
27
|
|
|
39
|
-
def action_vault_password(new_password:, **)
|
|
40
|
-
Aspera.assert(vault_required.respond_to?(:change_password), 'Vault does not support password change')
|
|
41
|
-
vault_required.change_password(new_password)
|
|
42
|
-
Result::Status.new('Vault password updated')
|
|
43
|
-
end
|
|
44
|
-
|
|
45
28
|
# @return [Keychain::Base] vault instance, raises if not configured
|
|
46
29
|
def vault_required
|
|
47
30
|
Aspera.assert(!vault.nil?, type: Cli::BadArgument) { 'Missing mandatory option: vault' }
|