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