aspera-cli 4.13.0 → 4.15.0
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 +81 -7
- data/CONTRIBUTING.md +22 -6
- data/README.md +2038 -1080
- data/bin/ascli +18 -9
- data/bin/asession +12 -14
- data/examples/dascli +1 -1
- data/examples/proxy.pac +1 -1
- data/examples/rubyc +24 -0
- data/lib/aspera/aoc.rb +219 -159
- data/lib/aspera/ascmd.rb +25 -14
- data/lib/aspera/cli/basic_auth_plugin.rb +12 -9
- data/lib/aspera/cli/error.rb +17 -0
- data/lib/aspera/cli/extended_value.rb +47 -12
- data/lib/aspera/cli/formatter.rb +260 -179
- data/lib/aspera/cli/hints.rb +80 -0
- data/lib/aspera/cli/main.rb +104 -156
- data/lib/aspera/cli/manager.rb +259 -209
- data/lib/aspera/cli/plugin.rb +123 -63
- data/lib/aspera/cli/plugins/alee.rb +2 -3
- data/lib/aspera/cli/plugins/aoc.rb +341 -261
- data/lib/aspera/cli/plugins/ats.rb +22 -21
- data/lib/aspera/cli/plugins/bss.rb +5 -5
- data/lib/aspera/cli/plugins/config.rb +578 -627
- data/lib/aspera/cli/plugins/console.rb +44 -6
- data/lib/aspera/cli/plugins/cos.rb +15 -17
- data/lib/aspera/cli/plugins/faspex.rb +114 -100
- data/lib/aspera/cli/plugins/faspex5.rb +411 -264
- data/lib/aspera/cli/plugins/node.rb +354 -259
- data/lib/aspera/cli/plugins/orchestrator.rb +61 -29
- data/lib/aspera/cli/plugins/preview.rb +82 -90
- data/lib/aspera/cli/plugins/server.rb +79 -32
- data/lib/aspera/cli/plugins/shares.rb +55 -42
- data/lib/aspera/cli/sync_actions.rb +68 -0
- data/lib/aspera/cli/transfer_agent.rb +66 -73
- data/lib/aspera/cli/transfer_progress.rb +74 -0
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/colors.rb +12 -8
- data/lib/aspera/command_line_builder.rb +14 -11
- data/lib/aspera/cos_node.rb +3 -2
- data/lib/aspera/data/6 +0 -0
- data/lib/aspera/environment.rb +24 -9
- data/lib/aspera/fasp/agent_aspera.rb +126 -0
- data/lib/aspera/fasp/agent_base.rb +31 -77
- data/lib/aspera/fasp/agent_connect.rb +25 -21
- data/lib/aspera/fasp/agent_direct.rb +89 -103
- data/lib/aspera/fasp/agent_httpgw.rb +231 -149
- data/lib/aspera/fasp/agent_node.rb +41 -34
- data/lib/aspera/fasp/agent_trsdk.rb +75 -32
- data/lib/aspera/fasp/error_info.rb +4 -2
- data/lib/aspera/fasp/faux_file.rb +52 -0
- data/lib/aspera/fasp/installation.rb +53 -195
- data/lib/aspera/fasp/management.rb +244 -0
- data/lib/aspera/fasp/parameters.rb +71 -37
- data/lib/aspera/fasp/parameters.yaml +76 -8
- data/lib/aspera/fasp/products.rb +162 -0
- data/lib/aspera/fasp/resume_policy.rb +3 -3
- data/lib/aspera/fasp/transfer_spec.rb +7 -6
- data/lib/aspera/fasp/uri.rb +26 -24
- data/lib/aspera/faspex_gw.rb +2 -2
- data/lib/aspera/faspex_postproc.rb +2 -2
- data/lib/aspera/hash_ext.rb +14 -4
- data/lib/aspera/json_rpc.rb +49 -0
- data/lib/aspera/keychain/macos_security.rb +13 -13
- data/lib/aspera/line_logger.rb +23 -0
- data/lib/aspera/log.rb +58 -16
- data/lib/aspera/node.rb +157 -92
- data/lib/aspera/oauth.rb +37 -19
- data/lib/aspera/open_application.rb +4 -4
- data/lib/aspera/persistency_action_once.rb +1 -1
- data/lib/aspera/persistency_folder.rb +2 -2
- data/lib/aspera/preview/file_types.rb +4 -2
- data/lib/aspera/preview/generator.rb +22 -35
- data/lib/aspera/preview/options.rb +2 -0
- data/lib/aspera/preview/terminal.rb +73 -16
- data/lib/aspera/preview/utils.rb +21 -28
- data/lib/aspera/proxy_auto_config.js +2 -2
- data/lib/aspera/rest.rb +136 -68
- data/lib/aspera/rest_call_error.rb +1 -1
- data/lib/aspera/rest_error_analyzer.rb +15 -14
- data/lib/aspera/rest_errors_aspera.rb +37 -34
- data/lib/aspera/secret_hider.rb +18 -15
- data/lib/aspera/ssh.rb +5 -2
- data/lib/aspera/sync.rb +127 -119
- data/lib/aspera/temp_file_manager.rb +10 -3
- data/lib/aspera/web_auth.rb +10 -7
- data/lib/aspera/web_server_simple.rb +9 -4
- data.tar.gz.sig +0 -0
- metadata +34 -17
- metadata.gz.sig +0 -0
- data/docs/test_env.conf +0 -186
- data/lib/aspera/cli/listener/line_dump.rb +0 -19
- data/lib/aspera/cli/listener/logger.rb +0 -22
- data/lib/aspera/cli/listener/progress.rb +0 -50
- data/lib/aspera/cli/listener/progress_multi.rb +0 -84
- data/lib/aspera/cli/plugins/sync.rb +0 -44
- data/lib/aspera/data/7 +0 -0
- data/lib/aspera/fasp/listener.rb +0 -13
data/lib/aspera/cli/formatter.rb
CHANGED
@@ -1,102 +1,180 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# cspell:ignore jsonpp
|
3
4
|
require 'aspera/secret_hider'
|
5
|
+
require 'aspera/environment'
|
4
6
|
require 'terminal-table'
|
5
7
|
require 'yaml'
|
6
8
|
require 'pp'
|
7
9
|
|
8
10
|
module Aspera
|
9
11
|
module Cli
|
12
|
+
CONF_OVERVIEW_KEYS = %w[preset parameter value].freeze
|
13
|
+
# This class is used to transform a complex structure into a simple hash
|
14
|
+
class Flattener
|
15
|
+
def initialize
|
16
|
+
@result = nil
|
17
|
+
end
|
18
|
+
|
19
|
+
# General method
|
20
|
+
def flatten(something)
|
21
|
+
raise 'only Hash' unless something.is_a?(Hash)
|
22
|
+
@result = {}
|
23
|
+
flatten_any(something, '')
|
24
|
+
return @result
|
25
|
+
end
|
26
|
+
|
27
|
+
# Special method for configuration overview
|
28
|
+
def config_over(something)
|
29
|
+
@result = []
|
30
|
+
something.each do |config, preset|
|
31
|
+
preset.each do |parameter, value|
|
32
|
+
@result.push(CONF_OVERVIEW_KEYS.zip([config, parameter, value]).to_h)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
return @result
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
# Recursive function to flatten any type
|
41
|
+
# @param something [Object] to be flattened
|
42
|
+
# @param name [String] name of englobing key
|
43
|
+
def flatten_any(something, name)
|
44
|
+
if something.is_a?(Hash)
|
45
|
+
flattened_hash(something, name)
|
46
|
+
elsif something.is_a?(Array)
|
47
|
+
flatten_array(something, name)
|
48
|
+
elsif something.is_a?(String) && something.empty?
|
49
|
+
@result[name] = Formatter.special('empty string')
|
50
|
+
elsif something.nil?
|
51
|
+
@result[name] = Formatter.special('null')
|
52
|
+
# elsif something.eql?(true) || something.eql?(false)
|
53
|
+
# @result[name] = something
|
54
|
+
else
|
55
|
+
@result[name] = something
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Recursive function to flatten an array
|
60
|
+
# @param array [Array] to be flattened
|
61
|
+
# @param name [String] name of englobing key
|
62
|
+
def flatten_array(array, name)
|
63
|
+
if array.empty?
|
64
|
+
@result[name] = Formatter.special('empty list')
|
65
|
+
elsif array.all?(String)
|
66
|
+
@result[name] = array.join("\n")
|
67
|
+
elsif array.all?{|i| i.is_a?(Hash) && i.keys.eql?(%w[name])}
|
68
|
+
@result[name] = array.map(&:values).join(', ')
|
69
|
+
elsif array.all?{|i| i.is_a?(Hash) && i.keys.sort.eql?(%w[name value])}
|
70
|
+
flattened_hash(array.each_with_object({}){|i, h|h[i['name']] = i['value']}, name)
|
71
|
+
else
|
72
|
+
array.each_with_index { |item, index| flatten_any(item, "#{name}.#{index}")}
|
73
|
+
end
|
74
|
+
nil
|
75
|
+
end
|
76
|
+
|
77
|
+
# Recursive function to flatten a Hash
|
78
|
+
# @param hash [Hash] to be flattened
|
79
|
+
# @param name [String] name of englobing key
|
80
|
+
def flattened_hash(hash, name)
|
81
|
+
prefix = name.empty? ? '' : "#{name}."
|
82
|
+
hash.each do |k, v|
|
83
|
+
flatten_any(v, "#{prefix}#{k}")
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end # class
|
87
|
+
|
10
88
|
# Take care of output
|
11
89
|
class Formatter
|
12
|
-
|
13
|
-
FIELDS_DEFAULT = 'DEF'
|
90
|
+
FIELDS_LESS = '-'
|
14
91
|
CSV_RECORD_SEPARATOR = "\n"
|
15
92
|
CSV_FIELD_SEPARATOR = ','
|
16
93
|
# supported output formats
|
17
94
|
DISPLAY_FORMATS = %i[text nagios ruby json jsonpp yaml table csv].freeze
|
18
95
|
# user output levels
|
19
96
|
DISPLAY_LEVELS = %i[info data error].freeze
|
20
|
-
CONF_OVERVIEW_KEYS = %w[config parameter value].freeze
|
21
97
|
|
22
|
-
private_constant :
|
23
|
-
|
98
|
+
private_constant :DISPLAY_FORMATS, :DISPLAY_LEVELS, :CSV_RECORD_SEPARATOR, :CSV_FIELD_SEPARATOR
|
99
|
+
# prefix to display error messages in user messages (terminal)
|
100
|
+
ERROR_FLASH = 'ERROR:'.bg_red.gray.blink.freeze
|
101
|
+
WARNING_FLASH = 'WARNING:'.bg_brown.black.blink.freeze
|
102
|
+
HINT_FLASH = 'HINT:'.bg_green.gray.blink.freeze
|
24
103
|
|
25
104
|
class << self
|
26
|
-
# special
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
105
|
+
# Highlight special values
|
106
|
+
def special(what, use_colors: $stdout.isatty)
|
107
|
+
result = "<#{what}>"
|
108
|
+
if use_colors
|
109
|
+
result = if %w[null empty].any?{|s|what.include?(s)}
|
110
|
+
result.dim
|
111
|
+
else
|
112
|
+
result.reverse_color
|
34
113
|
end
|
35
|
-
hash.delete(k)
|
36
114
|
end
|
115
|
+
return result
|
37
116
|
end
|
38
117
|
|
39
|
-
def
|
40
|
-
|
41
|
-
|
42
|
-
preset.each do |parameter, value|
|
43
|
-
r.push(CONF_OVERVIEW_KEYS.zip([config, parameter, SecretHider.deep_remove_secret(value).to_s]).to_h)
|
44
|
-
end
|
45
|
-
end
|
46
|
-
return r
|
118
|
+
def all_but(list)
|
119
|
+
list = [list] unless list.is_a?(Array)
|
120
|
+
return list.map{|i|"#{FIELDS_LESS}#{i}"}.unshift(ExtendedValue::ALL)
|
47
121
|
end
|
48
122
|
|
49
|
-
def
|
50
|
-
|
123
|
+
def tick(yes)
|
124
|
+
result =
|
125
|
+
if Environment.use_unicode?
|
126
|
+
if yes
|
127
|
+
"\u2713"
|
128
|
+
else
|
129
|
+
"\u2717"
|
130
|
+
end
|
131
|
+
elsif yes
|
132
|
+
'Y'
|
133
|
+
else
|
134
|
+
' '
|
135
|
+
end
|
136
|
+
return result.green if yes
|
137
|
+
return result.red
|
51
138
|
end
|
52
139
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
Log.log.debug{"(#{expand_last})[#{simple_hash?(source)}] -#{source.values}- \n-#{source}-"}
|
60
|
-
source.each do |k, v|
|
61
|
-
if v.is_a?(Hash) && !(expand_last && simple_hash?(v))
|
62
|
-
flattened_object(v, result: result, prefix: prefix + k.to_s + '.', expand_last: expand_last)
|
63
|
-
else
|
64
|
-
result[prefix + k.to_s] = v
|
140
|
+
def auto_type(data)
|
141
|
+
result = {type: :other_struct, data: data}
|
142
|
+
result[:type] = :single_object if result[:data].is_a?(Hash)
|
143
|
+
if result[:data].is_a?(Array)
|
144
|
+
if result[:data].all?(Hash)
|
145
|
+
result[:type] = :object_list
|
65
146
|
end
|
66
147
|
end
|
67
148
|
return result
|
68
149
|
end
|
150
|
+
end # self
|
151
|
+
|
152
|
+
# initialize the formatter
|
153
|
+
def initialize
|
154
|
+
@options = {}
|
69
155
|
end
|
70
156
|
|
71
|
-
|
72
|
-
|
157
|
+
def option_handler(option_symbol, operation, value=nil)
|
158
|
+
case operation
|
159
|
+
when :set then @options[option_symbol] = value
|
160
|
+
when :get then return @options[option_symbol]
|
161
|
+
else raise "internal error: no such operation: #{operation}"
|
162
|
+
end
|
163
|
+
nil
|
164
|
+
end
|
73
165
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
opt_mgr.set_obj_attr(:fields, self, :option_fields)
|
87
|
-
opt_mgr.set_obj_attr(:select, self, :option_select)
|
88
|
-
opt_mgr.set_obj_attr(:table_style, self, :option_table_style)
|
89
|
-
opt_mgr.set_obj_attr(:flat_hash, self, :option_flat_hash)
|
90
|
-
opt_mgr.set_obj_attr(:transpose_single, self, :option_transpose_single)
|
91
|
-
opt_mgr.set_obj_attr(:show_secrets, self, :option_show_secrets)
|
92
|
-
opt_mgr.add_opt_list(:format, DISPLAY_FORMATS, 'output format')
|
93
|
-
opt_mgr.add_opt_list(:display, DISPLAY_LEVELS, 'output only some information')
|
94
|
-
opt_mgr.add_opt_simple(:fields, "comma separated list of fields, or #{FIELDS_ALL}, or #{FIELDS_DEFAULT}")
|
95
|
-
opt_mgr.add_opt_simple(:select, 'select only some items in lists, extended value: hash (column, value)')
|
96
|
-
opt_mgr.add_opt_simple(:table_style, 'table display style')
|
97
|
-
opt_mgr.add_opt_boolean(:flat_hash, 'display hash values as additional keys')
|
98
|
-
opt_mgr.add_opt_boolean(:transpose_single, 'single object fields output vertically')
|
99
|
-
opt_mgr.add_opt_boolean(:show_secrets, 'show secrets on command output')
|
166
|
+
def declare_options(options)
|
167
|
+
options.declare(:format, 'Output format', values: DISPLAY_FORMATS, handler: {o: self, m: :option_handler}, default: :table)
|
168
|
+
options.declare(:display, 'Output only some information', values: DISPLAY_LEVELS, handler: {o: self, m: :option_handler}, default: :info)
|
169
|
+
options.declare(
|
170
|
+
:fields, "Comma separated list of: fields, or #{ExtendedValue::ALL}, or #{ExtendedValue::DEF}", handler: {o: self, m: :option_handler},
|
171
|
+
types: [String, Array, Regexp, Proc],
|
172
|
+
default: ExtendedValue::DEF)
|
173
|
+
options.declare(:select, 'Select only some items in lists: column, value', types: [Hash, Proc], handler: {o: self, m: :option_handler})
|
174
|
+
options.declare(:table_style, 'Table display style', handler: {o: self, m: :option_handler}, default: ':.:')
|
175
|
+
options.declare(:flat_hash, 'Display deep values as additional keys', values: :bool, handler: {o: self, m: :option_handler}, default: true)
|
176
|
+
options.declare(:transpose_single, 'Single object fields output vertically', values: :bool, handler: {o: self, m: :option_handler}, default: true)
|
177
|
+
options.declare(:show_secrets, 'Show secrets on command output', values: :bool, handler: {o: self, m: :option_handler}, default: false)
|
100
178
|
end
|
101
179
|
|
102
180
|
# main output method
|
@@ -105,8 +183,8 @@ module Aspera
|
|
105
183
|
# error: always displayed on stderr
|
106
184
|
def display_message(message_level, message)
|
107
185
|
case message_level
|
108
|
-
when :data then $stdout.puts(message) unless @
|
109
|
-
when :info then $stdout.puts(message) if @
|
186
|
+
when :data then $stdout.puts(message) unless @options[:display].eql?(:error)
|
187
|
+
when :info then $stdout.puts(message) if @options[:display].eql?(:info)
|
110
188
|
when :error then $stderr.puts(message)
|
111
189
|
else raise "wrong message_level:#{message_level}"
|
112
190
|
end
|
@@ -117,156 +195,159 @@ module Aspera
|
|
117
195
|
end
|
118
196
|
|
119
197
|
def display_item_count(number, total)
|
198
|
+
number = number.to_i
|
199
|
+
total = total.to_i
|
200
|
+
return if total.eql?(0) && number.eql?(0)
|
120
201
|
count_msg = "Items: #{number}/#{total}"
|
121
|
-
count_msg = count_msg.bg_red unless number.
|
202
|
+
count_msg = count_msg.bg_red unless number.eql?(total)
|
122
203
|
display_status(count_msg)
|
123
204
|
end
|
124
205
|
|
125
|
-
def
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
206
|
+
def all_fields(data)
|
207
|
+
data.each_with_object({}){|v, m|v.each_key{|c|m[c] = true}}.keys
|
208
|
+
end
|
209
|
+
|
210
|
+
# this method computes the list of fields to display
|
211
|
+
# data: array of hash
|
212
|
+
# default: list of fields to display by default (may contain special values)
|
213
|
+
def compute_fields(data, default)
|
214
|
+
Log.log.debug{"compute_fields: data:#{data.class} default:#{default.class} #{default}"}
|
215
|
+
request =
|
216
|
+
case @options[:fields]
|
217
|
+
when NilClass then [ExtendedValue::DEF]
|
218
|
+
when String then @options[:fields].split(',')
|
219
|
+
when Array then @options[:fields]
|
220
|
+
when Regexp then return all_fields(data).select{|i|i.match(@options[:fields])}
|
221
|
+
when Proc then return all_fields(data).select{|i|@options[:fields].call(i)}
|
222
|
+
else raise "internal error: option: fields: #{@options[:fields]}"
|
223
|
+
end
|
224
|
+
result = []
|
225
|
+
until request.empty?
|
226
|
+
item = request.shift
|
227
|
+
removal = false
|
228
|
+
if item[0].eql?(FIELDS_LESS)
|
229
|
+
removal = true
|
230
|
+
item = item[1..-1]
|
231
|
+
end
|
232
|
+
case item
|
233
|
+
when ExtendedValue::ALL
|
234
|
+
# get the list of all column names used in all lines, not just first one, as all lines may have different columns
|
235
|
+
request.unshift(*all_fields(data))
|
236
|
+
when ExtendedValue::DEF
|
237
|
+
default = all_fields(data).select{|i|default.call(i)} if default.is_a?(Proc)
|
238
|
+
default = all_fields(data) if default.nil?
|
239
|
+
request.unshift(*default)
|
240
|
+
else
|
241
|
+
if removal
|
242
|
+
result = result.reject{|i|i.eql?(item)}
|
243
|
+
else
|
244
|
+
result.push(item)
|
245
|
+
end
|
131
246
|
end
|
132
|
-
return results[:fields]
|
133
247
|
end
|
134
|
-
return
|
135
|
-
return table_rows_hash_val.first.keys
|
248
|
+
return result
|
136
249
|
end
|
137
250
|
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
251
|
+
# this method displays a table
|
252
|
+
# object_array: array of hash
|
253
|
+
# fields: list of column names
|
254
|
+
def display_table(object_array, fields)
|
255
|
+
raise 'internal error: no field specified' if fields.nil?
|
256
|
+
case @options[:select]
|
257
|
+
when Proc
|
258
|
+
object_array.select!{|i|@options[:select].call(i)}
|
259
|
+
when Hash
|
260
|
+
@options[:select].each{|k, v|object_array.select!{|i|i[k].eql?(v)}}
|
261
|
+
end
|
262
|
+
if object_array.empty?
|
263
|
+
# no display for csv
|
264
|
+
display_message(:info, Formatter.special('empty')) if @options[:format].eql?(:table)
|
265
|
+
return
|
266
|
+
end
|
267
|
+
if object_array.length == 1 && fields.length == 1
|
268
|
+
display_message(:data, object_array.first[fields.first])
|
269
|
+
return
|
270
|
+
end
|
271
|
+
# Special case if only one row (it could be object_list or single_object)
|
272
|
+
if @options[:transpose_single] && object_array.length == 1
|
273
|
+
new_columns = %i[key value]
|
274
|
+
single = object_array.first
|
275
|
+
object_array = fields.map { |i| new_columns.zip([i, single[i]]).to_h }
|
276
|
+
fields = new_columns
|
277
|
+
end
|
278
|
+
Log.log.debug{Log.dump(:object_array, object_array)}
|
279
|
+
# convert data to string, and keep only display fields
|
280
|
+
final_table_rows = object_array.map { |r| fields.map { |c| r[c].to_s } }
|
281
|
+
# here : fields : list of column names
|
282
|
+
case @options[:format]
|
283
|
+
when :table
|
284
|
+
style = @options[:table_style].chars
|
285
|
+
# display the table !
|
286
|
+
display_message(:data, Terminal::Table.new(
|
287
|
+
headings: fields,
|
288
|
+
rows: final_table_rows,
|
289
|
+
border_x: style[0],
|
290
|
+
border_y: style[1],
|
291
|
+
border_i: style[2]))
|
292
|
+
when :csv
|
293
|
+
display_message(:data, final_table_rows.map{|t| t.join(CSV_FIELD_SEPARATOR)}.join(CSV_RECORD_SEPARATOR))
|
294
|
+
end
|
142
295
|
end
|
143
296
|
|
144
297
|
# this method displays the results, especially the table format
|
145
298
|
def display_results(results)
|
299
|
+
raise "INTERNAL ERROR, result unsupported key: #{results.keys - %i[type data fields name]}" unless (results.keys - %i[type data fields name]).empty?
|
300
|
+
# :type :data :fields :name
|
146
301
|
raise "INTERNAL ERROR, result must be Hash (got: #{results.class}: #{results})" unless results.is_a?(Hash)
|
147
|
-
raise
|
302
|
+
raise "INTERNAL ERROR, result must have type (#{results})" unless results.key?(:type)
|
148
303
|
raise 'INTERNAL ERROR, result must have data' unless results.key?(:data) || %i[empty nothing].include?(results[:type])
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
SecretHider.deep_remove_secret(res_data, is_name_value: is_config_overview) unless @option_show_secrets || @option_display.eql?(:data)
|
153
|
-
# comma separated list in string format
|
154
|
-
user_asked_fields_list_str = @option_fields
|
155
|
-
case @option_format
|
304
|
+
Log.log.debug{"display_results: #{results[:data].class} #{results[:type]}"}
|
305
|
+
SecretHider.deep_remove_secret(results[:data]) unless @options[:show_secrets] || @options[:display].eql?(:data)
|
306
|
+
case @options[:format]
|
156
307
|
when :text
|
157
|
-
display_message(:data,
|
308
|
+
display_message(:data, results[:data].to_s)
|
158
309
|
when :nagios
|
159
|
-
Nagios.process(
|
310
|
+
Nagios.process(results[:data])
|
160
311
|
when :ruby
|
161
|
-
display_message(:data, PP.pp(
|
312
|
+
display_message(:data, PP.pp(results[:data], +''))
|
162
313
|
when :json
|
163
|
-
display_message(:data, JSON.generate(
|
314
|
+
display_message(:data, JSON.generate(results[:data]))
|
164
315
|
when :jsonpp
|
165
|
-
display_message(:data, JSON.pretty_generate(
|
316
|
+
display_message(:data, JSON.pretty_generate(results[:data]))
|
166
317
|
when :yaml
|
167
|
-
display_message(:data,
|
318
|
+
display_message(:data, results[:data].to_yaml)
|
168
319
|
when :table, :csv
|
169
|
-
if !@option_transpose_single && results[:type].eql?(:single_object)
|
170
|
-
results[:type] = :object_list
|
171
|
-
res_data = [res_data]
|
172
|
-
end
|
173
320
|
case results[:type]
|
174
|
-
when :
|
175
|
-
|
321
|
+
when :config_over
|
322
|
+
display_table(Flattener.new.config_over(results[:data]), CONF_OVERVIEW_KEYS)
|
323
|
+
when :object_list, :single_object
|
324
|
+
obj_list = results[:data]
|
325
|
+
obj_list = [obj_list] if results[:type].eql?(:single_object)
|
326
|
+
raise "internal error: expecting Array: got #{obj_list.class}" unless obj_list.is_a?(Array)
|
327
|
+
raise 'internal error: expecting Array of Hash' unless obj_list.all?(Hash)
|
176
328
|
# :object_list is an array of hash tables, where key=colum name
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
table_rows_hash_val.map!{|obj|self.class.flattened_object(obj, expand_last: results[:option_expand_last])}
|
181
|
-
end
|
182
|
-
final_table_columns =
|
183
|
-
case user_asked_fields_list_str
|
184
|
-
when FIELDS_DEFAULT then result_default_fields(results, table_rows_hash_val)
|
185
|
-
when FIELDS_ALL then result_all_fields(results, table_rows_hash_val)
|
186
|
-
else
|
187
|
-
if user_asked_fields_list_str.start_with?('+')
|
188
|
-
result_default_fields(results, table_rows_hash_val).push(*user_asked_fields_list_str.gsub(/^\+/, '').split(','))
|
189
|
-
elsif user_asked_fields_list_str.start_with?('-')
|
190
|
-
result_default_fields(results, table_rows_hash_val).reject{|i| user_asked_fields_list_str.gsub(/^-/, '').split(',').include?(i)}
|
191
|
-
else
|
192
|
-
user_asked_fields_list_str.split(',')
|
193
|
-
end
|
194
|
-
end
|
195
|
-
when :single_object # goes to table display
|
196
|
-
# :single_object is a simple hash table (can be nested)
|
197
|
-
raise "internal error: expecting Hash: got #{res_data.class}: #{res_data}" unless res_data.is_a?(Hash)
|
198
|
-
final_table_columns = results[:columns] || %w[key value]
|
199
|
-
if @option_flat_hash
|
200
|
-
res_data = self.class.flattened_object(res_data, expand_last: results[:option_expand_last])
|
201
|
-
self.class.flatten_name_value_list(res_data)
|
202
|
-
end
|
203
|
-
asked_fields =
|
204
|
-
case user_asked_fields_list_str
|
205
|
-
when FIELDS_DEFAULT then results[:fields] || res_data.keys
|
206
|
-
when FIELDS_ALL then res_data.keys
|
207
|
-
else user_asked_fields_list_str.split(',')
|
208
|
-
end
|
209
|
-
table_rows_hash_val = asked_fields.map { |i| { final_table_columns.first => i, final_table_columns.last => res_data[i] } }
|
210
|
-
when :value_list # goes to table display
|
329
|
+
obj_list = obj_list.map{|obj|Flattener.new.flatten(obj)} if @options[:flat_hash]
|
330
|
+
display_table(obj_list, compute_fields(obj_list, results[:fields]))
|
331
|
+
when :value_list
|
211
332
|
# :value_list is a simple array of values, name of column provided in the :name
|
212
|
-
|
213
|
-
table_rows_hash_val = res_data.map { |i| { results[:name] => i } }
|
333
|
+
display_table(results[:data].map { |i| { results[:name] => i } }, [results[:name]])
|
214
334
|
when :empty # no table
|
215
|
-
display_message(:info, 'empty')
|
335
|
+
display_message(:info, Formatter.special('empty'))
|
216
336
|
return
|
217
337
|
when :nothing # no result expected
|
218
338
|
Log.log.debug('no result expected')
|
219
|
-
return
|
220
339
|
when :status # no table
|
221
340
|
# :status displays a simple message
|
222
|
-
display_message(:info,
|
223
|
-
return
|
341
|
+
display_message(:info, results[:data])
|
224
342
|
when :text # no table
|
225
343
|
# :status displays a simple message
|
226
|
-
display_message(:data,
|
227
|
-
return
|
344
|
+
display_message(:data, results[:data])
|
228
345
|
when :other_struct # no table
|
229
346
|
# :other_struct is any other type of structure
|
230
|
-
display_message(:data, PP.pp(
|
231
|
-
return
|
347
|
+
display_message(:data, PP.pp(results[:data], +''))
|
232
348
|
else
|
233
349
|
raise "unknown data type: #{results[:type]}"
|
234
350
|
end
|
235
|
-
# here we expect: table_rows_hash_val and final_table_columns
|
236
|
-
raise 'no field specified' if final_table_columns.nil?
|
237
|
-
if table_rows_hash_val.empty?
|
238
|
-
display_message(:info, 'empty'.gray) unless @option_format.eql?(:csv)
|
239
|
-
return
|
240
|
-
end
|
241
|
-
# convert to string with special function. here table_rows_hash_val is an array of hash
|
242
|
-
table_rows_hash_val = results[:textify].call(table_rows_hash_val) if results.key?(:textify)
|
243
|
-
unless @option_select.nil? || (@option_select.respond_to?(:empty?) && @option_select.empty?)
|
244
|
-
raise CliBadArgument, "expecting hash for select, have #{@option_select.class}: #{@option_select}" unless @option_select.is_a?(Hash)
|
245
|
-
@option_select.each{|k, v|table_rows_hash_val.select!{|i|i[k].eql?(v)}}
|
246
|
-
end
|
247
|
-
# convert data to string, and keep only display fields
|
248
|
-
final_table_rows = table_rows_hash_val.map { |r| final_table_columns.map { |c| r[c].to_s } }
|
249
|
-
# here : final_table_columns : list of column names
|
250
|
-
# here: final_table_rows : array of list of value
|
251
|
-
case @option_format
|
252
|
-
when :table
|
253
|
-
style = @option_table_style.chars
|
254
|
-
# display the table !
|
255
|
-
# display_message(:data,Text::Table.new(
|
256
|
-
# head: final_table_columns,
|
257
|
-
# rows: final_table_rows,
|
258
|
-
# horizontal_boundary: style[0],
|
259
|
-
# vertical_boundary: style[1],
|
260
|
-
# boundary_intersection: style[2]))
|
261
|
-
display_message(:data, Terminal::Table.new(
|
262
|
-
headings: final_table_columns,
|
263
|
-
rows: final_table_rows,
|
264
|
-
border_x: style[0],
|
265
|
-
border_y: style[1],
|
266
|
-
border_i: style[2]))
|
267
|
-
when :csv
|
268
|
-
display_message(:data, final_table_rows.map{|t| t.join(CSV_FIELD_SEPARATOR)}.join(CSV_RECORD_SEPARATOR))
|
269
|
-
end
|
270
351
|
end
|
271
352
|
end
|
272
353
|
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'aspera/fasp/error'
|
4
|
+
require 'aspera/rest'
|
5
|
+
require 'net/ssh'
|
6
|
+
require 'openssl'
|
7
|
+
|
8
|
+
module Aspera
|
9
|
+
module Cli
|
10
|
+
# Provide hints on errors
|
11
|
+
class Hints
|
12
|
+
# Well know issues that users may get
|
13
|
+
ERROR_HINTS = [
|
14
|
+
{
|
15
|
+
exception: Fasp::Error,
|
16
|
+
match: 'Remote host is not who we expected',
|
17
|
+
remediation: [
|
18
|
+
'For this specific error, refer to:',
|
19
|
+
"#{SRC_URL}#error-remote-host-is-not-who-we-expected",
|
20
|
+
'Add this to arguments:',
|
21
|
+
%q{--ts=@json:'{"sshfp":null}'"}
|
22
|
+
]
|
23
|
+
},
|
24
|
+
{
|
25
|
+
exception: Aspera::RestCallError,
|
26
|
+
match: /Signature has expired/,
|
27
|
+
remediation: [
|
28
|
+
'There is too much time difference between your computer and the server',
|
29
|
+
'Check your local time: is time synchronization enabled?'
|
30
|
+
]
|
31
|
+
},
|
32
|
+
{
|
33
|
+
exception: OpenSSL::SSL::SSLError,
|
34
|
+
match: /(does not match the server certificate|certificate verify failed)/,
|
35
|
+
remediation: [
|
36
|
+
'You can ignore SSL errors with option:',
|
37
|
+
'--insecure=yes'
|
38
|
+
]
|
39
|
+
},
|
40
|
+
{
|
41
|
+
exception: OpenSSL::PKey::RSAError,
|
42
|
+
match: /Neither PUB key nor PRIV key/,
|
43
|
+
remediation: [
|
44
|
+
'option: private_key expects a key PEM value, not path to file',
|
45
|
+
'if you provide a path: prefix with @file:',
|
46
|
+
'e.g. --private-key=@file:/path/to/key.pem'
|
47
|
+
]
|
48
|
+
}
|
49
|
+
]
|
50
|
+
|
51
|
+
private_constant :ERROR_HINTS
|
52
|
+
|
53
|
+
class << self
|
54
|
+
def hint_for(error, formatter)
|
55
|
+
ERROR_HINTS.each do |hint|
|
56
|
+
next unless error.is_a?(hint[:exception])
|
57
|
+
message = error.message
|
58
|
+
matches = hint[:match]
|
59
|
+
matches = [matches] unless matches.is_a?(Array)
|
60
|
+
matches.each do |m|
|
61
|
+
case m
|
62
|
+
when String
|
63
|
+
next unless message.eql?(m)
|
64
|
+
when Regexp
|
65
|
+
next unless message.match?(m)
|
66
|
+
else
|
67
|
+
Log.log.warn("Internal error: hint is a #{m.class}")
|
68
|
+
next
|
69
|
+
end
|
70
|
+
remediation = hint[:remediation]
|
71
|
+
remediation = [remediation] unless remediation.is_a?(Array)
|
72
|
+
remediation.each{|r|formatter.display_message(:error, "#{Formatter::HINT_FLASH} #{r}")}
|
73
|
+
break
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|