aspera-cli 4.26.2 → 4.27.1
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 +310 -21
- data/CONTRIBUTING.md +99 -14
- data/TODO.md +50 -0
- data/bin/ascli +3 -3
- data/docs/README.md +10977 -0
- data/docs/test-mcp-with-ai.md +202 -0
- data/lib/aspera/agent/base.rb +29 -7
- data/lib/aspera/agent/connect.rb +58 -21
- data/lib/aspera/agent/desktop.rb +53 -18
- data/lib/aspera/agent/direct.rb +71 -40
- data/lib/aspera/agent/factory.rb +3 -3
- data/lib/aspera/agent/httpgw.rb +55 -14
- data/lib/aspera/agent/node.rb +34 -2
- data/lib/aspera/agent/transferd.rb +48 -12
- data/lib/aspera/api/alee.rb +1 -1
- data/lib/aspera/api/aoc.rb +42 -44
- data/lib/aspera/api/cos_node.rb +10 -9
- data/lib/aspera/api/faspex.rb +16 -15
- data/lib/aspera/api/httpgw.rb +23 -23
- data/lib/aspera/api/node.rb +51 -70
- data/lib/aspera/api/queries/bss_subscription_account.graphql +18 -0
- data/lib/aspera/api/queries/bss_subscription_usage.graphql +18 -0
- data/lib/aspera/ascmd.rb +32 -30
- data/lib/aspera/ascp/installation.rb +75 -69
- data/lib/aspera/ascp/management.rb +7 -8
- data/lib/aspera/assert.rb +12 -13
- data/lib/aspera/cli/ascp_actions.rb +155 -0
- data/lib/aspera/cli/async_transfer_store.rb +125 -0
- data/lib/aspera/cli/bootstrapper.rb +197 -0
- data/lib/aspera/cli/command_registry.rb +145 -0
- data/lib/aspera/cli/command_spec.rb +141 -0
- data/lib/aspera/cli/context.rb +19 -6
- data/lib/aspera/cli/error.rb +16 -0
- data/lib/aspera/cli/extended_value.rb +27 -26
- data/lib/aspera/cli/formatter.rb +95 -59
- data/lib/aspera/cli/gem_checker.rb +65 -0
- data/lib/aspera/cli/hints.rb +3 -3
- data/lib/aspera/cli/http.rb +74 -30
- data/lib/aspera/cli/info.rb +2 -0
- data/lib/aspera/cli/mailer.rb +97 -0
- data/lib/aspera/cli/mcp_tool.rb +225 -0
- data/lib/aspera/cli/option_declarator.rb +77 -0
- data/lib/aspera/cli/options.schema.yaml +634 -7
- data/lib/aspera/cli/parser.rb +1217 -0
- data/lib/aspera/cli/plugins/alee.rb +20 -22
- data/lib/aspera/cli/plugins/aoc.rb +1178 -881
- data/lib/aspera/cli/plugins/ats.rb +205 -162
- data/lib/aspera/cli/plugins/base.rb +703 -175
- data/lib/aspera/cli/plugins/basic_auth.rb +7 -9
- data/lib/aspera/cli/plugins/config.rb +434 -757
- data/lib/aspera/cli/plugins/console.rb +107 -65
- data/lib/aspera/cli/plugins/cos.rb +46 -33
- data/lib/aspera/cli/plugins/factory.rb +6 -6
- data/lib/aspera/cli/plugins/faspex5.rb +627 -388
- data/lib/aspera/cli/plugins/faspio.rb +54 -51
- data/lib/aspera/cli/plugins/httpgw.rb +18 -25
- data/lib/aspera/cli/plugins/mcp.rb +279 -0
- data/lib/aspera/cli/plugins/node.rb +988 -840
- data/lib/aspera/cli/plugins/oauth.rb +7 -10
- data/lib/aspera/cli/plugins/orchestrator.rb +112 -135
- data/lib/aspera/cli/plugins/preview.rb +195 -157
- data/lib/aspera/cli/plugins/server.rb +141 -91
- data/lib/aspera/cli/plugins/shares.rb +343 -110
- data/lib/aspera/cli/preset_actions.rb +159 -0
- data/lib/aspera/cli/preset_manager.rb +82 -39
- data/lib/aspera/cli/result.rb +78 -26
- data/lib/aspera/cli/runner.rb +323 -153
- data/lib/aspera/cli/secret_finder.rb +40 -0
- data/lib/aspera/cli/special_values.rb +1 -0
- data/lib/aspera/cli/sync_actions.rb +89 -64
- data/lib/aspera/cli/terminal_formatter.rb +3 -3
- data/lib/aspera/cli/transfer_actions.rb +90 -0
- data/lib/aspera/cli/transfer_agent.rb +129 -64
- data/lib/aspera/cli/transfer_progress.rb +9 -9
- data/lib/aspera/cli/vault_manager.rb +79 -0
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/cli/wizard.rb +28 -30
- data/lib/aspera/colors.rb +3 -3
- data/lib/aspera/command_line_builder.rb +34 -34
- data/lib/aspera/command_line_converter.rb +1 -1
- data/lib/aspera/coverage.rb +1 -2
- data/lib/aspera/data_repository.rb +2 -1
- data/lib/aspera/dot_container.rb +12 -12
- data/lib/aspera/environment.rb +30 -23
- data/lib/aspera/exec_spec.rb +13 -0
- data/lib/aspera/faspex_gw.rb +5 -5
- data/lib/aspera/faspex_postproc.rb +16 -10
- data/lib/aspera/graphql.rb +37 -0
- data/lib/aspera/hash_ext.rb +8 -2
- data/lib/aspera/json_rpc/client.rb +62 -0
- data/lib/aspera/json_rpc/version.rb +7 -0
- data/lib/aspera/keychain/base.rb +10 -3
- data/lib/aspera/keychain/encrypted_hash.rb +92 -17
- data/lib/aspera/keychain/factory.rb +20 -8
- data/lib/aspera/keychain/hashicorp_vault.rb +1 -1
- data/lib/aspera/keychain/macos_security.rb +23 -25
- data/lib/aspera/keychain/one_password_api.rb +86 -0
- data/lib/aspera/keychain/one_password_base.rb +34 -0
- data/lib/aspera/keychain/one_password_cli.rb +98 -0
- data/lib/aspera/link_header.rb +82 -0
- data/lib/aspera/log.rb +29 -15
- data/lib/aspera/markdown.rb +90 -7
- data/lib/aspera/nagios.rb +8 -8
- data/lib/aspera/node_simulator.rb +35 -27
- data/lib/aspera/oauth/base.rb +9 -10
- data/lib/aspera/oauth/boot.rb +5 -5
- data/lib/aspera/oauth/factory.rb +12 -10
- data/lib/aspera/oauth/jwt.rb +9 -11
- data/lib/aspera/oauth/web.rb +6 -6
- data/lib/aspera/persistency_action_once.rb +12 -12
- data/lib/aspera/persistency_folder.rb +18 -11
- data/lib/aspera/preview/file_types.rb +5 -5
- data/lib/aspera/preview/generator.rb +52 -49
- data/lib/aspera/preview/options.rb +3 -2
- data/lib/aspera/preview/terminal.rb +5 -5
- data/lib/aspera/preview/utils.rb +66 -19
- data/lib/aspera/products/connect.rb +2 -2
- data/lib/aspera/products/desktop.rb +1 -1
- data/lib/aspera/products/other.rb +3 -3
- data/lib/aspera/products/transferd.rb +4 -4
- data/lib/aspera/proxy_auto_config.rb +15 -14
- data/lib/aspera/rest.rb +159 -151
- data/lib/aspera/rest_error_analyzer.rb +6 -6
- data/lib/aspera/rest_errors_aspera.rb +0 -10
- data/lib/aspera/rest_list.rb +11 -10
- data/lib/aspera/schema/IBM Aspera Node API-4.4.6.yaml +6232 -0
- data/lib/aspera/schema/IBM Aspera faspio Gateway API-1.0.0.yaml +249 -0
- data/lib/aspera/schema/IBM Aspera on Cloud API-0.2.6-enhanced.yaml +1853 -510
- data/lib/aspera/schema/IBM_Aspera_Shares.yaml +4653 -0
- data/lib/aspera/schema/documentation.rb +65 -28
- data/lib/aspera/schema/reader.rb +136 -13
- data/lib/aspera/schema/registry.rb +60 -8
- data/lib/aspera/secret_hider.rb +12 -7
- data/lib/aspera/ssh.rb +64 -31
- data/lib/aspera/ssl.rb +5 -5
- data/lib/aspera/sync/conf.schema.yaml +2 -2
- data/lib/aspera/sync/database.rb +2 -2
- data/lib/aspera/sync/operations.rb +22 -25
- data/lib/aspera/temp_file_manager.rb +25 -5
- data/lib/aspera/timer_limiter.rb +1 -1
- data/lib/aspera/transfer/faux_file.rb +24 -11
- data/lib/aspera/transfer/parameters.rb +36 -34
- data/lib/aspera/transfer/result.rb +74 -0
- data/lib/aspera/transfer/resumer.rb +10 -10
- data/lib/aspera/transfer/spec.rb +18 -1
- data/lib/aspera/transfer/spec.schema.yaml +12 -3
- data/lib/aspera/uri_reader.rb +52 -15
- data/lib/aspera/web_auth.rb +8 -7
- data/lib/aspera/web_server_simple.rb +15 -12
- data/lib/aspera/yaml.rb +5 -4
- data.tar.gz.sig +0 -0
- metadata +34 -6
- metadata.gz.sig +0 -0
- data/lib/aspera/cli/manager.rb +0 -766
- data/lib/aspera/cli/plugins/faspex.rb +0 -539
- data/lib/aspera/json_rpc.rb +0 -52
- data/lib/aspera/transfer/uri.rb +0 -56
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'aspera/cli/extended_value'
|
|
4
|
+
require 'aspera/dot_container'
|
|
4
5
|
require 'aspera/cli/version'
|
|
5
6
|
require 'aspera/cli/info'
|
|
6
7
|
require 'aspera/log'
|
|
7
8
|
require 'aspera/assert'
|
|
9
|
+
require 'aspera/yaml'
|
|
8
10
|
require 'digest'
|
|
9
11
|
require 'yaml'
|
|
10
12
|
|
|
@@ -16,10 +18,12 @@ module Aspera
|
|
|
16
18
|
class PresetManager
|
|
17
19
|
# Reserved keys in the YAML config file
|
|
18
20
|
module Key
|
|
19
|
-
CONFIG
|
|
20
|
-
VERSION
|
|
21
|
-
DEFAULTS
|
|
22
|
-
GLOBAL
|
|
21
|
+
CONFIG = 'config'
|
|
22
|
+
VERSION = 'version'
|
|
23
|
+
DEFAULTS = 'default'
|
|
24
|
+
GLOBAL = 'global_common_defaults'
|
|
25
|
+
# Prefix for comment/meta keys ignored during preset parsing (e.g. _comment)
|
|
26
|
+
META_PREFIX = '_'
|
|
23
27
|
end
|
|
24
28
|
|
|
25
29
|
GLOBAL_DEFAULT_KEYWORD = 'GLOBAL'
|
|
@@ -39,7 +43,7 @@ module Aspera
|
|
|
39
43
|
read_config_file
|
|
40
44
|
end
|
|
41
45
|
|
|
42
|
-
attr_reader :config_presets, :use_plugin_defaults
|
|
46
|
+
attr_reader :config_file, :config_presets, :use_plugin_defaults
|
|
43
47
|
attr_writer :use_plugin_defaults
|
|
44
48
|
|
|
45
49
|
# ------------------------------------------------------------------
|
|
@@ -54,26 +58,26 @@ module Aspera
|
|
|
54
58
|
# Read and validate the YAML config file.
|
|
55
59
|
# Sets @config_presets and @checksum_on_disk.
|
|
56
60
|
def read_config_file
|
|
57
|
-
Log.log.debug{"config file is: #{@config_file}".red}
|
|
61
|
+
Log.log.debug { "config file is: #{@config_file}".red }
|
|
58
62
|
if File.exist?(@config_file)
|
|
59
|
-
Log.log.debug{"loading #{@config_file}"}
|
|
60
|
-
@config_presets =
|
|
63
|
+
Log.log.debug { "loading #{@config_file}" }
|
|
64
|
+
@config_presets = Yaml.safe_load(File.read(@config_file))
|
|
61
65
|
@checksum_on_disk = checksum
|
|
62
66
|
else
|
|
63
|
-
Log.log.warn{"No config file found. New configuration file: #{@config_file}"}
|
|
67
|
+
Log.log.warn { "No config file found. New configuration file: #{@config_file}" }
|
|
64
68
|
@config_presets = {Key::CONFIG => {Key::VERSION => 'new file'}}
|
|
65
|
-
# @checksum_on_disk remains nil
|
|
69
|
+
# @checksum_on_disk remains nil: will be saved on first write
|
|
66
70
|
end
|
|
67
71
|
validate_config_presets!
|
|
68
72
|
rescue Psych::SyntaxError => e
|
|
69
73
|
Log.log.error('YAML error in config file')
|
|
70
74
|
raise e
|
|
71
75
|
rescue StandardError => e
|
|
72
|
-
Log.log.debug{"-> #{e.class.name} : #{e}"}
|
|
76
|
+
Log.log.debug { "-> #{e.class.name} : #{e}" }
|
|
73
77
|
if File.exist?(@config_file)
|
|
74
78
|
new_name = "#{@config_file}.pre#{Cli::VERSION}.manual_conversion_needed"
|
|
75
79
|
File.rename(@config_file, new_name)
|
|
76
|
-
Log.log.warn{"Renamed config file to #{new_name}."}
|
|
80
|
+
Log.log.warn { "Renamed config file to #{new_name}." }
|
|
77
81
|
Log.log.warn('Manual Conversion is required. Next time, a new empty file will be created.')
|
|
78
82
|
end
|
|
79
83
|
raise Cli::Error, e.to_s
|
|
@@ -82,13 +86,13 @@ module Aspera
|
|
|
82
86
|
# Save to disk only if content changed since last load/save.
|
|
83
87
|
# @return [Boolean] true if actually written
|
|
84
88
|
def save_if_needed
|
|
85
|
-
|
|
89
|
+
Aspera.assert(!@config_presets.nil?, type: Cli::Error) { 'no configuration loaded' }
|
|
86
90
|
current = checksum
|
|
87
91
|
return false if @checksum_on_disk.eql?(current)
|
|
88
92
|
FileUtils.mkdir_p(File.dirname(@config_file))
|
|
89
93
|
Environment.restrict_file_access(File.dirname(@config_file))
|
|
90
|
-
Log.log.info{"Saving config file: #{@config_file}"}
|
|
91
|
-
Environment.write_file_restricted(@config_file, force: true){ @config_presets.to_yaml }
|
|
94
|
+
Log.log.info { "Saving config file: #{@config_file}" }
|
|
95
|
+
Environment.write_file_restricted(@config_file, force: true) { @config_presets.to_yaml }
|
|
92
96
|
@checksum_on_disk = current
|
|
93
97
|
true
|
|
94
98
|
end
|
|
@@ -100,10 +104,10 @@ module Aspera
|
|
|
100
104
|
# @return [String, nil] name of the default preset for a plugin, or nil
|
|
101
105
|
def plugin_default_name(plugin_name_sym)
|
|
102
106
|
Aspera.assert(!@config_presets.nil?, 'config_presets shall be defined')
|
|
103
|
-
return
|
|
104
|
-
return
|
|
105
|
-
Aspera.assert_type(@config_presets[Key::DEFAULTS], Hash){'default section'}
|
|
106
|
-
return
|
|
107
|
+
return unless @use_plugin_defaults
|
|
108
|
+
return unless @config_presets.key?(Key::DEFAULTS)
|
|
109
|
+
Aspera.assert_type(@config_presets[Key::DEFAULTS], Hash) { 'default section' }
|
|
110
|
+
return unless @config_presets[Key::DEFAULTS].key?(plugin_name_sym.to_s)
|
|
107
111
|
default_name = @config_presets[Key::DEFAULTS][plugin_name_sym.to_s]
|
|
108
112
|
unless @config_presets.key?(default_name)
|
|
109
113
|
Log.log.error do
|
|
@@ -114,7 +118,7 @@ module Aspera
|
|
|
114
118
|
end
|
|
115
119
|
raise Cli::Error, "No such preset: #{default_name}"
|
|
116
120
|
end
|
|
117
|
-
Aspera.assert_type(@config_presets[default_name], Hash, type: Cli::Error){'preset type'}
|
|
121
|
+
Aspera.assert_type(@config_presets[default_name], Hash, type: Cli::Error) { 'preset type' }
|
|
118
122
|
default_name
|
|
119
123
|
end
|
|
120
124
|
|
|
@@ -123,38 +127,62 @@ module Aspera
|
|
|
123
127
|
# @param config_name [String]
|
|
124
128
|
# @param include_path [Array] guard against include loops
|
|
125
129
|
def by_name(config_name, include_path = [])
|
|
126
|
-
|
|
130
|
+
Aspera.assert(!include_path.include?(config_name), type: Cli::Error) { 'loop in include' }
|
|
127
131
|
include_path = include_path.clone
|
|
128
132
|
current = @config_presets
|
|
129
133
|
config_name.split(PRESET_DIG_SEPARATOR).each do |name|
|
|
130
|
-
Aspera.assert_type(current, Hash, type: Cli::Error){"sub key: #{include_path}"}
|
|
134
|
+
Aspera.assert_type(current, Hash, type: Cli::Error) { "sub key: #{include_path}" }
|
|
131
135
|
include_path.push(name)
|
|
132
136
|
current = current[name]
|
|
133
|
-
|
|
137
|
+
Aspera.assert(!current.nil?, type: Cli::Error) { "Unknown config preset: #{include_path}" }
|
|
134
138
|
end
|
|
135
139
|
current = self.class.deep_clone(current) unless current.is_a?(String)
|
|
140
|
+
current.delete_if { |k, _| k.to_s.start_with?(Key::META_PREFIX) } if current.is_a?(Hash)
|
|
136
141
|
ExtendedValue.instance.evaluate(current, context: 'preset')
|
|
137
142
|
end
|
|
138
143
|
|
|
139
144
|
# Set a single key in a preset hash (creates the preset if absent).
|
|
145
|
+
# If param_name contains dots, it is treated as a dot-path and deep-merged into the preset.
|
|
146
|
+
# If param_value is nil, the key is deleted instead.
|
|
140
147
|
def set_key(preset, param_name, param_value)
|
|
141
|
-
Aspera.assert_type(param_name, String, Symbol){'parameter'}
|
|
148
|
+
Aspera.assert_type(param_name, String, Symbol) { 'parameter' }
|
|
142
149
|
param_name = param_name.to_s
|
|
143
150
|
selected = @config_presets[preset]
|
|
144
151
|
if selected.nil?
|
|
145
|
-
Log.log.debug{"Unknown preset name: #{preset}, initializing"}
|
|
152
|
+
Log.log.debug { "Unknown preset name: #{preset}, initializing" }
|
|
146
153
|
selected = @config_presets[preset] = {}
|
|
147
154
|
end
|
|
148
|
-
Aspera.assert_type(selected, Hash){"#{preset}.#{param_name}"}
|
|
149
|
-
if
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
155
|
+
Aspera.assert_type(selected, Hash) { "#{preset}.#{param_name}" }
|
|
156
|
+
if param_name.include?(DotContainer::SEPARATOR)
|
|
157
|
+
keys = param_name.split(DotContainer::SEPARATOR)
|
|
158
|
+
# Navigate to the parent hash
|
|
159
|
+
parent = keys[0..-2].reduce(selected) do |h, k|
|
|
160
|
+
break nil unless h.is_a?(Hash)
|
|
161
|
+
h[k]
|
|
153
162
|
end
|
|
154
|
-
|
|
163
|
+
if param_value.nil?
|
|
164
|
+
parent&.delete(keys.last)
|
|
165
|
+
Log.log.info("Deleted: #{preset}: #{param_name}")
|
|
166
|
+
else
|
|
167
|
+
# dot-path: build nested hash and deep-merge into existing preset
|
|
168
|
+
nested = DotContainer.dotted_to_container(keys, param_value)
|
|
169
|
+
self.class.deep_merge!(selected, nested)
|
|
170
|
+
Log.log.info("Updated: #{preset}: #{param_name} <- #{param_value}")
|
|
171
|
+
end
|
|
172
|
+
elsif param_value.nil?
|
|
173
|
+
selected.delete(param_name)
|
|
174
|
+
Log.log.info("Deleted: #{preset}: #{param_name}")
|
|
175
|
+
else
|
|
176
|
+
if selected.key?(param_name)
|
|
177
|
+
if selected[param_name].eql?(param_value)
|
|
178
|
+
Log.log.warn { "keeping same value for #{preset}: #{param_name}: #{param_value}" }
|
|
179
|
+
return
|
|
180
|
+
end
|
|
181
|
+
Log.log.warn { "overwriting value for #{param_name}: #{selected[param_name]}" }
|
|
182
|
+
end
|
|
183
|
+
selected[param_name] = param_value
|
|
184
|
+
Log.log.info("Updated: #{preset}: #{param_name} <- #{param_value}")
|
|
155
185
|
end
|
|
156
|
-
selected[param_name] = param_value
|
|
157
|
-
Log.log.info("Updated: #{preset}: #{param_name} <- #{param_value}")
|
|
158
186
|
nil
|
|
159
187
|
end
|
|
160
188
|
|
|
@@ -191,7 +219,7 @@ module Aspera
|
|
|
191
219
|
# @return [Hash, nil]
|
|
192
220
|
def lookup_preset(url:, username:)
|
|
193
221
|
url = canonical_url(url)
|
|
194
|
-
Log.log.debug{"Lookup preset for #{username}@#{url}"}
|
|
222
|
+
Log.log.debug { "Lookup preset for #{username}@#{url}" }
|
|
195
223
|
@config_presets.each_value do |v|
|
|
196
224
|
next unless v.is_a?(Hash)
|
|
197
225
|
conf_url = v['url'].is_a?(String) ? canonical_url(v['url']) : nil
|
|
@@ -208,11 +236,26 @@ module Aspera
|
|
|
208
236
|
def deep_clone(val)
|
|
209
237
|
Marshal.load(Marshal.dump(val))
|
|
210
238
|
end
|
|
239
|
+
|
|
240
|
+
# Recursively merge src into dst in place (dst wins on scalar conflicts).
|
|
241
|
+
# @param dst [Hash] destination (modified in place)
|
|
242
|
+
# @param src [Hash] source
|
|
243
|
+
# @return [Hash] dst
|
|
244
|
+
def deep_merge!(dst, src)
|
|
245
|
+
src.each do |k, v|
|
|
246
|
+
if dst.key?(k) && dst[k].is_a?(Hash) && v.is_a?(Hash)
|
|
247
|
+
deep_merge!(dst[k], v)
|
|
248
|
+
else
|
|
249
|
+
dst[k] = v
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
dst
|
|
253
|
+
end
|
|
211
254
|
end
|
|
212
255
|
|
|
213
256
|
private
|
|
214
257
|
|
|
215
|
-
#
|
|
258
|
+
# Normalize a URL for comparison
|
|
216
259
|
def canonical_url(url)
|
|
217
260
|
url.chomp('/').sub(%r{^(https://[^/]+):443$}, '\1')
|
|
218
261
|
end
|
|
@@ -220,11 +263,11 @@ module Aspera
|
|
|
220
263
|
# Structural validation + compatibility fixes on @config_presets
|
|
221
264
|
def validate_config_presets!
|
|
222
265
|
Log.dump(:available_presets, @config_presets, level: :trace1)
|
|
223
|
-
Aspera.assert_type(@config_presets, Hash){'config file YAML'}
|
|
224
|
-
Aspera.assert(@config_presets.key?(Key::CONFIG)){"Cannot find key: #{Key::CONFIG}"}
|
|
266
|
+
Aspera.assert_type(@config_presets, Hash) { 'config file YAML' }
|
|
267
|
+
Aspera.assert(@config_presets.key?(Key::CONFIG)) { "Cannot find key: #{Key::CONFIG}" }
|
|
225
268
|
version = @config_presets[Key::CONFIG][Key::VERSION]
|
|
226
|
-
|
|
227
|
-
Log.log.debug{"conf version: #{version}"}
|
|
269
|
+
Aspera.assert(!version.nil?, type: Cli::Error) { 'No version found in config section.' }
|
|
270
|
+
Log.log.debug { "conf version: #{version}" }
|
|
228
271
|
# Fix bug in 4.4 (creating key "true" in "default" preset)
|
|
229
272
|
@config_presets[Key::DEFAULTS].delete(true) if @config_presets[Key::DEFAULTS].is_a?(Hash)
|
|
230
273
|
# Stamp with current version
|
data/lib/aspera/cli/result.rb
CHANGED
|
@@ -34,7 +34,7 @@ module Aspera
|
|
|
34
34
|
# Base implementation handles common formats: text, nagios, ruby, json, jsonpp, yaml
|
|
35
35
|
# Subclasses should call super first, then handle their specific formats
|
|
36
36
|
# @param formatter [Formatter] The formatter to use
|
|
37
|
-
# @return [
|
|
37
|
+
# @return [nil]
|
|
38
38
|
def format(formatter)
|
|
39
39
|
# Apply field filtering once for formats that need it
|
|
40
40
|
filtered_data = formatter.filter_list_on_fields(@data)
|
|
@@ -56,7 +56,7 @@ module Aspera
|
|
|
56
56
|
formatter.display_message(:data, formatter.special_format('null (no image)'))
|
|
57
57
|
return
|
|
58
58
|
end
|
|
59
|
-
Aspera.assert_type(@data, String){'image: URL or blob'}
|
|
59
|
+
Aspera.assert_type(@data, String) { 'image: URL or blob' }
|
|
60
60
|
# Check if URL
|
|
61
61
|
data =
|
|
62
62
|
begin
|
|
@@ -72,16 +72,18 @@ module Aspera
|
|
|
72
72
|
rescue URI::InvalidURIError
|
|
73
73
|
@data
|
|
74
74
|
end
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
75
|
+
unless data.eql?(:done)
|
|
76
|
+
# try base64
|
|
77
|
+
data = begin
|
|
78
|
+
Base64.strict_decode64(data)
|
|
79
|
+
rescue ArgumentError
|
|
80
|
+
data
|
|
81
|
+
end
|
|
82
|
+
# here, data is the image blob
|
|
83
|
+
formatter.display_message(:data, Preview::Terminal.build(data, **formatter.image_options))
|
|
80
84
|
end
|
|
81
|
-
# here, data is the image blob
|
|
82
|
-
formatter.display_message(:data, Preview::Terminal.build(data, **formatter.image_options)) unless data.eql?(:done)
|
|
83
85
|
else
|
|
84
|
-
Aspera.error_unexpected_value(formatter.format_type){'format'}
|
|
86
|
+
Aspera.error_unexpected_value(formatter.format_type) { 'format' }
|
|
85
87
|
end
|
|
86
88
|
end
|
|
87
89
|
end
|
|
@@ -128,7 +130,7 @@ module Aspera
|
|
|
128
130
|
# Status result (success, complete, etc.)
|
|
129
131
|
class Status < Result
|
|
130
132
|
def initialize(data)
|
|
131
|
-
Aspera.assert_type(data, String){'status result data'}
|
|
133
|
+
Aspera.assert_type(data, String) { 'status result data' }
|
|
132
134
|
super(data: data)
|
|
133
135
|
end
|
|
134
136
|
|
|
@@ -155,7 +157,7 @@ module Aspera
|
|
|
155
157
|
# Text result
|
|
156
158
|
class Text < Result
|
|
157
159
|
def initialize(data)
|
|
158
|
-
Aspera.assert_type(data, String, Integer, Symbol, type: ArgumentError){'text result data'}
|
|
160
|
+
Aspera.assert_type(data, String, Integer, Symbol, type: ArgumentError) { 'text result data' }
|
|
159
161
|
super(data: data)
|
|
160
162
|
end
|
|
161
163
|
|
|
@@ -183,13 +185,13 @@ module Aspera
|
|
|
183
185
|
# Image result (URL or blob)
|
|
184
186
|
class Image < Result
|
|
185
187
|
def initialize(data)
|
|
186
|
-
Aspera.assert_type(data, String, NilClass){'image result data'}
|
|
188
|
+
Aspera.assert_type(data, String, NilClass) { 'image result data' }
|
|
187
189
|
super(data: data)
|
|
188
190
|
end
|
|
189
191
|
|
|
190
192
|
def format(formatter)
|
|
191
193
|
# Force image format for Image results
|
|
192
|
-
formatter.
|
|
194
|
+
formatter.format_type = :image
|
|
193
195
|
super
|
|
194
196
|
end
|
|
195
197
|
end
|
|
@@ -197,7 +199,7 @@ module Aspera
|
|
|
197
199
|
# Single object result (Hash)
|
|
198
200
|
class SingleObject < Result
|
|
199
201
|
def initialize(data, fields: nil)
|
|
200
|
-
Aspera.assert_type(data, Hash){'single object result data'}
|
|
202
|
+
Aspera.assert_type(data, Hash) { 'single object result data' }
|
|
201
203
|
super(data: data, fields: fields)
|
|
202
204
|
end
|
|
203
205
|
|
|
@@ -212,7 +214,7 @@ module Aspera
|
|
|
212
214
|
# Create an Image result and format it
|
|
213
215
|
Image.new(@data[fields.first]).format(formatter)
|
|
214
216
|
when :table, :csv
|
|
215
|
-
Aspera.assert_type(@data, Hash){'result'}
|
|
217
|
+
Aspera.assert_type(@data, Hash) { 'result' }
|
|
216
218
|
if @data.empty?
|
|
217
219
|
formatter.display_message(:data, formatter.special_format('empty dict'))
|
|
218
220
|
else
|
|
@@ -234,9 +236,8 @@ module Aspera
|
|
|
234
236
|
# @param fields [Array<String>, Proc, nil] Fields to display in table/csv format
|
|
235
237
|
# @param total [Integer, nil] Total number of items available (for pagination display)
|
|
236
238
|
def initialize(data, fields: nil, total: nil)
|
|
237
|
-
Aspera.
|
|
238
|
-
|
|
239
|
-
Aspera.assert_type(total, Integer, NilClass){'total'}
|
|
239
|
+
Aspera.assert_array_all(data, Hash, type: ArgumentError) { 'object list result data' }
|
|
240
|
+
Aspera.assert_type(total, Integer, NilClass) { 'total' }
|
|
240
241
|
super(data: data, fields: fields)
|
|
241
242
|
@total = total
|
|
242
243
|
end
|
|
@@ -249,8 +250,8 @@ module Aspera
|
|
|
249
250
|
Aspera.assert(@data.length == 1, 'image display requires a single result', type: Cli::BadArgument)
|
|
250
251
|
SingleObject.new(@data.first).format(formatter)
|
|
251
252
|
when :table, :csv
|
|
252
|
-
Aspera.assert_array_all(@data, Hash){'result'}
|
|
253
|
-
data = formatter.flat_hash? ? @data.map{ |obj| DotContainer.new(obj).to_dotted} : @data
|
|
253
|
+
Aspera.assert_array_all(@data, Hash) { 'result' }
|
|
254
|
+
data = formatter.flat_hash? ? @data.map { |obj| DotContainer.new(obj).to_dotted } : @data
|
|
254
255
|
formatter.display_table(data, formatter.compute_fields(data, @fields), single: false)
|
|
255
256
|
else
|
|
256
257
|
super
|
|
@@ -263,8 +264,8 @@ module Aspera
|
|
|
263
264
|
attr_reader :name
|
|
264
265
|
|
|
265
266
|
def initialize(data, name: 'id')
|
|
266
|
-
Aspera.assert_type(data, Array){'value list result data'}
|
|
267
|
-
Aspera.assert_type(name, String){'value list name'}
|
|
267
|
+
Aspera.assert_type(data, Array) { 'value list result data' }
|
|
268
|
+
Aspera.assert_type(name, String) { 'value list name' }
|
|
268
269
|
super(data: data)
|
|
269
270
|
@name = name
|
|
270
271
|
end
|
|
@@ -272,7 +273,17 @@ module Aspera
|
|
|
272
273
|
def format(formatter)
|
|
273
274
|
case formatter.format_type
|
|
274
275
|
when :table, :csv
|
|
275
|
-
formatter.display_table(@data.map{ |i| {@name => i}}, [@name])
|
|
276
|
+
formatter.display_table(@data.map { |i| {@name => i} }, [@name])
|
|
277
|
+
when :text
|
|
278
|
+
formatter.display_message(:data, @data.join("\n"))
|
|
279
|
+
when :ruby
|
|
280
|
+
formatter.display_message(:data, PP.pp(@data, +''))
|
|
281
|
+
when :json
|
|
282
|
+
formatter.display_message(:data, JSON.generate(@data))
|
|
283
|
+
when :jsonpp
|
|
284
|
+
formatter.display_message(:data, JSON.pretty_generate(@data))
|
|
285
|
+
when :yaml
|
|
286
|
+
formatter.display_message(:data, YAML.dump(@data))
|
|
276
287
|
else
|
|
277
288
|
super
|
|
278
289
|
end
|
|
@@ -280,6 +291,47 @@ module Aspera
|
|
|
280
291
|
end
|
|
281
292
|
|
|
282
293
|
class << self
|
|
294
|
+
# Execute a bulk operation over a list of already-resolved items and return a CLI result.
|
|
295
|
+
# This is a pure helper: it reads nothing from the CLI.
|
|
296
|
+
# @param items [Array] List of items to process (one element when non-bulk)
|
|
297
|
+
# @param is_bulk [Boolean] Whether the caller is in bulk mode
|
|
298
|
+
# @param command [Symbol] Operation name (:create, :delete, …) used to build the past-tense status string
|
|
299
|
+
# @param id_result [String] Key in the result Hash used as item identifier
|
|
300
|
+
# @param fields [Object] Fields hint passed to the Result constructor (non-bulk only, when not :default)
|
|
301
|
+
# @param bfail [Boolean] When true, re-raise errors; when false, capture them as a status string
|
|
302
|
+
# @yieldparam item [Object] Each item in +items+
|
|
303
|
+
# @yieldreturn [Hash, Array, nil] REST response; a Hash replaces the default id-keyed result
|
|
304
|
+
# @return [Result::ObjectList, Result::SingleObject]
|
|
305
|
+
def bulk(items, is_bulk:, command:, id_result: 'id', fields: :default, bfail: true)
|
|
306
|
+
Aspera.assert(block_given?, 'missing block')
|
|
307
|
+
Log.log.warn('Empty list given for bulk operation') if items.empty?
|
|
308
|
+
Log.dump(:bulk_operation, items)
|
|
309
|
+
result_list = []
|
|
310
|
+
items.each do |item|
|
|
311
|
+
result = {id_result => item}
|
|
312
|
+
begin
|
|
313
|
+
res = yield(item)
|
|
314
|
+
result = res if res.is_a?(Hash)
|
|
315
|
+
# TODO: remove when faspio gw api fixes this
|
|
316
|
+
result = res.first if res.is_a?(Array) && res.first.is_a?(Hash)
|
|
317
|
+
# Ensure the id_result key is always present even when the API response uses a different key name
|
|
318
|
+
result[id_result] = item unless result.key?(id_result)
|
|
319
|
+
result['status'] = "#{command}#{'e' unless command.to_s.end_with?('e')}d".gsub(/yed$/, 'ied')
|
|
320
|
+
rescue StandardError => e
|
|
321
|
+
raise e if bfail
|
|
322
|
+
result['status'] = e.to_s
|
|
323
|
+
end
|
|
324
|
+
result_list.push(result)
|
|
325
|
+
end
|
|
326
|
+
display_fields = [id_result, 'status']
|
|
327
|
+
if is_bulk
|
|
328
|
+
return ObjectList.new(result_list, fields: display_fields)
|
|
329
|
+
else
|
|
330
|
+
display_fields = fields unless fields.eql?(:default)
|
|
331
|
+
return SingleObject.new(result_list.first, fields: display_fields)
|
|
332
|
+
end
|
|
333
|
+
end
|
|
334
|
+
|
|
283
335
|
# Class method to automatically determine result type from data
|
|
284
336
|
# @param data [Object] the data to analyze and format
|
|
285
337
|
# @return [Result]
|
|
@@ -297,11 +349,11 @@ module Aspera
|
|
|
297
349
|
unsupported_types = all_types - scalar_types
|
|
298
350
|
return ValueList.new(data, name: 'list') if unsupported_types.empty?
|
|
299
351
|
|
|
300
|
-
Aspera.error_unexpected_value(unsupported_types){'list item types'}
|
|
352
|
+
Aspera.error_unexpected_value(unsupported_types) { 'list item types' }
|
|
301
353
|
when String, Integer, Symbol
|
|
302
354
|
Text.new(data)
|
|
303
355
|
else
|
|
304
|
-
Aspera.error_unexpected_value(data.class.name){'result type'}
|
|
356
|
+
Aspera.error_unexpected_value(data.class.name) { 'result type' }
|
|
305
357
|
end
|
|
306
358
|
end
|
|
307
359
|
end
|