aspera-cli 4.20.0 → 4.21.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.
Potentially problematic release.
This version of aspera-cli might be problematic. Click here for more details.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +20 -2
- data/README.md +281 -156
- data/bin/asession +2 -2
- data/lib/aspera/agent/alpha.rb +7 -12
- data/lib/aspera/agent/connect.rb +19 -1
- data/lib/aspera/agent/direct.rb +20 -29
- data/lib/aspera/agent/node.rb +1 -11
- data/lib/aspera/agent/trsdk.rb +4 -25
- data/lib/aspera/api/aoc.rb +5 -0
- data/lib/aspera/api/node.rb +45 -28
- data/lib/aspera/ascp/installation.rb +69 -38
- data/lib/aspera/ascp/management.rb +27 -6
- data/lib/aspera/cli/formatter.rb +149 -141
- data/lib/aspera/cli/info.rb +1 -1
- data/lib/aspera/cli/manager.rb +1 -0
- data/lib/aspera/cli/plugin.rb +2 -2
- data/lib/aspera/cli/plugins/aoc.rb +27 -17
- data/lib/aspera/cli/plugins/config.rb +31 -21
- data/lib/aspera/cli/plugins/faspex.rb +1 -1
- data/lib/aspera/cli/plugins/faspex5.rb +11 -3
- data/lib/aspera/cli/plugins/node.rb +44 -38
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/command_line_builder.rb +1 -1
- data/lib/aspera/environment.rb +5 -6
- data/lib/aspera/node_simulator.rb +228 -112
- data/lib/aspera/oauth/base.rb +31 -42
- data/lib/aspera/oauth/factory.rb +41 -2
- data/lib/aspera/persistency_folder.rb +20 -2
- data/lib/aspera/preview/generator.rb +1 -1
- data/lib/aspera/preview/utils.rb +1 -1
- data/lib/aspera/products/alpha.rb +30 -0
- data/lib/aspera/products/connect.rb +48 -0
- data/lib/aspera/products/other.rb +82 -0
- data/lib/aspera/products/trsdk.rb +54 -0
- data/lib/aspera/rest.rb +18 -13
- data/lib/aspera/ssh.rb +28 -24
- data/lib/aspera/transfer/spec.yaml +22 -20
- data.tar.gz.sig +0 -0
- metadata +21 -4
- metadata.gz.sig +0 -0
- data/lib/aspera/ascp/products.rb +0 -168
|
@@ -3,11 +3,16 @@
|
|
|
3
3
|
# cspell:ignore protobuf ckpt
|
|
4
4
|
require 'aspera/environment'
|
|
5
5
|
require 'aspera/data_repository'
|
|
6
|
-
require 'aspera/ascp/products'
|
|
7
6
|
require 'aspera/log'
|
|
8
7
|
require 'aspera/rest'
|
|
9
8
|
require 'aspera/assert'
|
|
10
9
|
require 'aspera/web_server_simple'
|
|
10
|
+
require 'aspera/cli/info'
|
|
11
|
+
require 'aspera/cli/version'
|
|
12
|
+
require 'aspera/products/alpha'
|
|
13
|
+
require 'aspera/products/connect'
|
|
14
|
+
require 'aspera/products/trsdk'
|
|
15
|
+
require 'aspera/products/other'
|
|
11
16
|
require 'English'
|
|
12
17
|
require 'singleton'
|
|
13
18
|
require 'xmlsimple'
|
|
@@ -20,7 +25,7 @@ module Aspera
|
|
|
20
25
|
# Singleton that tells where to find ascp and other local resources (keys..) , using the "path(:name)" method.
|
|
21
26
|
# It is used by object : AgentDirect to find necessary resources
|
|
22
27
|
# By default it takes the first Aspera product found
|
|
23
|
-
#
|
|
28
|
+
# The user can specify ascp location by calling:
|
|
24
29
|
# Installation.instance.use_ascp_from_product(product_name)
|
|
25
30
|
# or
|
|
26
31
|
# Installation.instance.ascp_path=""
|
|
@@ -46,12 +51,16 @@ module Aspera
|
|
|
46
51
|
TRANSFER_SDK_LOCATION_URL = 'https://ibm.biz/sdk_location'
|
|
47
52
|
FILE_SCHEME_PREFIX = 'file:///'
|
|
48
53
|
SDK_ARCHIVE_FOLDERS = ['/bin/', '/aspera/'].freeze
|
|
54
|
+
# filename for ascp with optional extension (Windows)
|
|
49
55
|
private_constant :EXT_RUBY_PROTOBUF, :RB_SDK_SUBFOLDER, :DEFAULT_ASPERA_CONF, :FILES, :TRANSFER_SDK_LOCATION_URL, :FILE_SCHEME_PREFIX
|
|
50
56
|
# options for SSH client private key
|
|
51
57
|
CLIENT_SSH_KEY_OPTIONS = %i{dsa_rsa rsa per_client}.freeze
|
|
52
58
|
|
|
53
59
|
# set ascp executable path
|
|
54
60
|
def ascp_path=(v)
|
|
61
|
+
Aspera.assert_type(v, String)
|
|
62
|
+
Aspera.assert(!v.empty?) {'ascp path cannot be empty: check your config file'}
|
|
63
|
+
Aspera.assert(File.exist?(v)) {"No such file: [#{v}]"}
|
|
55
64
|
@path_to_ascp = v
|
|
56
65
|
end
|
|
57
66
|
|
|
@@ -59,31 +68,19 @@ module Aspera
|
|
|
59
68
|
path(:ascp)
|
|
60
69
|
end
|
|
61
70
|
|
|
62
|
-
#
|
|
71
|
+
# Compatibility
|
|
63
72
|
def sdk_folder=(v)
|
|
64
|
-
|
|
65
|
-
@sdk_dir = v
|
|
66
|
-
sdk_folder
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
# backward compatibility in sample program
|
|
70
|
-
alias_method :folder=, :sdk_folder=
|
|
71
|
-
|
|
72
|
-
# @return the path to folder where SDK is installed
|
|
73
|
-
def sdk_folder
|
|
74
|
-
raise 'SDK path was ot initialized' if @sdk_dir.nil?
|
|
75
|
-
FileUtils.mkdir_p(@sdk_dir)
|
|
76
|
-
@sdk_dir
|
|
73
|
+
Products::Trsdk.sdk_directory = v
|
|
77
74
|
end
|
|
78
75
|
|
|
79
76
|
# find ascp in named product (use value : FIRST_FOUND='FIRST' to just use first one)
|
|
80
|
-
# or select one from
|
|
77
|
+
# or select one from installed_products()
|
|
81
78
|
def use_ascp_from_product(product_name)
|
|
82
79
|
if product_name.eql?(FIRST_FOUND)
|
|
83
|
-
pl =
|
|
84
|
-
raise "
|
|
80
|
+
pl = installed_products.first
|
|
81
|
+
raise "no Aspera transfer module or SDK found.\nRefer to the manual or install SDK with command:\nascli conf ascp install" if pl.nil?
|
|
85
82
|
else
|
|
86
|
-
pl =
|
|
83
|
+
pl = installed_products.find{|i|i[:name].eql?(product_name)}
|
|
87
84
|
raise "no such product installed: #{product_name}" if pl.nil?
|
|
88
85
|
end
|
|
89
86
|
self.ascp_path = pl[:ascp_path]
|
|
@@ -103,7 +100,7 @@ module Aspera
|
|
|
103
100
|
end
|
|
104
101
|
|
|
105
102
|
def check_or_create_sdk_file(filename, force: false, &block)
|
|
106
|
-
return Environment.write_file_restricted(File.join(
|
|
103
|
+
return Environment.write_file_restricted(File.join(Products::Trsdk.sdk_directory, filename), force: force, mode: 0o644, &block)
|
|
107
104
|
end
|
|
108
105
|
|
|
109
106
|
# get path of one resource file of currently activated product
|
|
@@ -118,7 +115,7 @@ module Aspera
|
|
|
118
115
|
file = @path_to_ascp.gsub('ascp', k.to_s)
|
|
119
116
|
when :transferd
|
|
120
117
|
file_is_optional = true
|
|
121
|
-
file =
|
|
118
|
+
file = Products::Trsdk.transferd_path
|
|
122
119
|
when :ssh_private_dsa, :ssh_private_rsa
|
|
123
120
|
# assume last 3 letters are type
|
|
124
121
|
type = k.to_s[-3..-1].to_sym
|
|
@@ -128,8 +125,8 @@ module Aspera
|
|
|
128
125
|
when :aspera_conf
|
|
129
126
|
file = check_or_create_sdk_file('aspera.conf') {DEFAULT_ASPERA_CONF}
|
|
130
127
|
when :fallback_certificate, :fallback_private_key
|
|
131
|
-
file_key = File.join(
|
|
132
|
-
file_cert = File.join(
|
|
128
|
+
file_key = File.join(Products::Trsdk.sdk_directory, 'aspera_fallback_cert_private_key.pem')
|
|
129
|
+
file_cert = File.join(Products::Trsdk.sdk_directory, 'aspera_fallback_cert.pem')
|
|
133
130
|
if !File.exist?(file_key) || !File.exist?(file_cert)
|
|
134
131
|
require 'openssl'
|
|
135
132
|
# create new self signed certificate for http fallback
|
|
@@ -143,7 +140,7 @@ module Aspera
|
|
|
143
140
|
else Aspera.error_unexpected_value(k)
|
|
144
141
|
end
|
|
145
142
|
return nil if file_is_optional && !File.exist?(file)
|
|
146
|
-
Aspera.assert(File.exist?(file)){"no such file: #{file}"}
|
|
143
|
+
Aspera.assert(File.exist?(file)){"no such file for #{k}: [#{file}]"}
|
|
147
144
|
return file
|
|
148
145
|
end
|
|
149
146
|
|
|
@@ -239,7 +236,14 @@ module Aspera
|
|
|
239
236
|
# Loads YAML from cloud with locations of SDK archives for all platforms
|
|
240
237
|
# @return location structure
|
|
241
238
|
def sdk_locations
|
|
242
|
-
yaml_text = Aspera::Rest.new(
|
|
239
|
+
yaml_text = Aspera::Rest.new(
|
|
240
|
+
base_url: TRANSFER_SDK_LOCATION_URL,
|
|
241
|
+
redirect_max: 3).call(
|
|
242
|
+
operation: 'GET',
|
|
243
|
+
headers: {
|
|
244
|
+
'Referer' => "http://version.#{Cli::VERSION}"
|
|
245
|
+
}
|
|
246
|
+
)[:data]
|
|
243
247
|
YAML.load(yaml_text)
|
|
244
248
|
end
|
|
245
249
|
|
|
@@ -255,6 +259,7 @@ module Aspera
|
|
|
255
259
|
return info.first['url']
|
|
256
260
|
end
|
|
257
261
|
|
|
262
|
+
# @param &block called with entry information
|
|
258
263
|
def extract_archive_files(sdk_archive_path)
|
|
259
264
|
raise 'missing block' unless block_given?
|
|
260
265
|
case sdk_archive_path
|
|
@@ -265,7 +270,7 @@ module Aspera
|
|
|
265
270
|
Zip::File.open(sdk_archive_path) do |zip_file|
|
|
266
271
|
zip_file.each do |entry|
|
|
267
272
|
next if entry.name.end_with?('/')
|
|
268
|
-
yield(entry.name, entry.get_input_stream)
|
|
273
|
+
yield(entry.name, entry.get_input_stream, nil)
|
|
269
274
|
end
|
|
270
275
|
end
|
|
271
276
|
# Other Unixes use tar.gz
|
|
@@ -276,7 +281,7 @@ module Aspera
|
|
|
276
281
|
Gem::Package::TarReader.new(gzip) do |tar|
|
|
277
282
|
tar.each do |entry|
|
|
278
283
|
next if entry.directory?
|
|
279
|
-
yield(entry.full_name, entry)
|
|
284
|
+
yield(entry.full_name, entry, entry.symlink? ? entry.header.linkname : nil)
|
|
280
285
|
end
|
|
281
286
|
end
|
|
282
287
|
end
|
|
@@ -287,11 +292,15 @@ module Aspera
|
|
|
287
292
|
|
|
288
293
|
# download aspera SDK or use local file
|
|
289
294
|
# extracts ascp binary for current system architecture
|
|
290
|
-
# @param url
|
|
295
|
+
# @param url [String] URL to SDK archive, or SpecialValues::DEF
|
|
296
|
+
# @param folder [String] destination
|
|
297
|
+
# @param backup [Bool]
|
|
298
|
+
# @param with_exe [Bool]
|
|
299
|
+
# @param &block [Proc] a lambda that receives a file path from archive and tells detination sub folder, or nil to not extract
|
|
291
300
|
# @return ascp version (from execution)
|
|
292
301
|
def install_sdk(url: nil, folder: nil, backup: true, with_exe: true, &block)
|
|
293
302
|
url = sdk_url_for_platform if url.nil? || url.eql?('DEF')
|
|
294
|
-
folder =
|
|
303
|
+
folder = Products::Trsdk.sdk_directory if folder.nil?
|
|
295
304
|
subfolder_lambda = block
|
|
296
305
|
if subfolder_lambda.nil?
|
|
297
306
|
subfolder_lambda = ->(name) do
|
|
@@ -318,13 +327,16 @@ module Aspera
|
|
|
318
327
|
File.rename(folder, "#{folder}.#{Time.now.strftime('%Y%m%d%H%M%S')}")
|
|
319
328
|
# TODO: delete old archives ?
|
|
320
329
|
end
|
|
321
|
-
extract_archive_files(sdk_archive_path) do |entry_name, entry_stream|
|
|
330
|
+
extract_archive_files(sdk_archive_path) do |entry_name, entry_stream, link_target|
|
|
322
331
|
subfolder = subfolder_lambda.call(entry_name)
|
|
323
332
|
next if subfolder.nil?
|
|
324
333
|
dest_folder = File.join(folder, subfolder)
|
|
325
334
|
FileUtils.mkdir_p(dest_folder)
|
|
326
|
-
File.
|
|
327
|
-
|
|
335
|
+
new_file = File.join(dest_folder, File.basename(entry_name))
|
|
336
|
+
if link_target.nil?
|
|
337
|
+
File.open(new_file, 'wb') { |output_stream|IO.copy_stream(entry_stream, output_stream)}
|
|
338
|
+
else
|
|
339
|
+
File.symlink(link_target, new_file)
|
|
328
340
|
end
|
|
329
341
|
end
|
|
330
342
|
File.unlink(sdk_archive_path) rescue nil if delete_archive # Windows may give error
|
|
@@ -332,7 +344,7 @@ module Aspera
|
|
|
332
344
|
# ensure license file are generated so that ascp invocation for version works
|
|
333
345
|
path(:aspera_license)
|
|
334
346
|
path(:aspera_conf)
|
|
335
|
-
sdk_ascp_file =
|
|
347
|
+
sdk_ascp_file = Environment.exe_file('ascp')
|
|
336
348
|
sdk_ascp_path = File.join(folder, sdk_ascp_file)
|
|
337
349
|
raise "No #{sdk_ascp_file} found in SDK archive" unless File.exist?(sdk_ascp_path)
|
|
338
350
|
EXE_FILES.each do |exe_sym|
|
|
@@ -340,13 +352,13 @@ module Aspera
|
|
|
340
352
|
Environment.restrict_file_access(exe_path, mode: 0o755) if File.exist?(exe_path)
|
|
341
353
|
end
|
|
342
354
|
sdk_ascp_version = get_ascp_version(sdk_ascp_path)
|
|
343
|
-
sdk_daemon_path =
|
|
355
|
+
sdk_daemon_path = Products::Trsdk.transferd_path
|
|
344
356
|
Log.log.warn{"No #{sdk_daemon_path} in SDK archive"} unless File.exist?(sdk_daemon_path)
|
|
345
357
|
Environment.restrict_file_access(sdk_daemon_path, mode: 0o755) if File.exist?(sdk_daemon_path)
|
|
346
358
|
transferd_version = get_exe_version(sdk_daemon_path, 'version')
|
|
347
359
|
sdk_name = 'IBM Aspera Transfer SDK'
|
|
348
360
|
sdk_version = transferd_version || sdk_ascp_version
|
|
349
|
-
File.write(File.join(folder, Products::INFO_META_FILE), "<product><name>#{sdk_name}</name><version>#{sdk_version}</version></product>")
|
|
361
|
+
File.write(File.join(folder, Products::Other::INFO_META_FILE), "<product><name>#{sdk_name}</name><version>#{sdk_version}</version></product>")
|
|
350
362
|
return sdk_name, sdk_version
|
|
351
363
|
end
|
|
352
364
|
|
|
@@ -358,10 +370,29 @@ module Aspera
|
|
|
358
370
|
def initialize
|
|
359
371
|
@path_to_ascp = nil
|
|
360
372
|
@sdk_dir = nil
|
|
373
|
+
@found_products = nil
|
|
361
374
|
end
|
|
362
375
|
|
|
363
|
-
|
|
364
|
-
|
|
376
|
+
public
|
|
377
|
+
|
|
378
|
+
# @return the list of installed products in format of product_locations_on_current_os
|
|
379
|
+
def installed_products
|
|
380
|
+
if @found_products.nil?
|
|
381
|
+
# :expected M app name is taken from the manifest if present, else defaults to this value
|
|
382
|
+
# :app_root M main folder for the application
|
|
383
|
+
# :log_root O location of log files (Linux uses syslog)
|
|
384
|
+
# :run_root O only for Connect Client, location of http port file
|
|
385
|
+
# :sub_bin O subfolder with executables, default : bin
|
|
386
|
+
scan_locations = Products::Trsdk.locations.concat(
|
|
387
|
+
Products::Alpha.locations,
|
|
388
|
+
Products::Connect.locations,
|
|
389
|
+
Products::Other::LOCATION_ON_THIS_OS
|
|
390
|
+
)
|
|
391
|
+
# .each {|item| item.deep_do {|h, _k, _v, _m|h.freeze}}.freeze
|
|
392
|
+
# search installed products: with ascp
|
|
393
|
+
@found_products = Products::Other.find(scan_locations)
|
|
394
|
+
end
|
|
395
|
+
return @found_products
|
|
365
396
|
end
|
|
366
397
|
end
|
|
367
398
|
end
|
|
@@ -198,18 +198,39 @@ module Aspera
|
|
|
198
198
|
BOOLEAN_FIELDS = %w[Encryption Remote RateLock MinRateLock PolicyLock FilesEncrypt FilesDecrypt VLinkLocalEnabled VLinkRemoteEnabled
|
|
199
199
|
MoveRange Keepalive TestLogin UseProxy Precalc RTTAutocorrect].freeze
|
|
200
200
|
BOOLEAN_TRUE = 'Yes'
|
|
201
|
+
|
|
202
|
+
private_constant :OPERATIONS, :PARAMETERS, :MGT_HEADER, :MGT_FRAME_SEPARATOR, :INTEGER_FIELDS, :BOOLEAN_FIELDS, :BOOLEAN_TRUE
|
|
201
203
|
# cspell: enable
|
|
202
204
|
|
|
203
205
|
class << self
|
|
204
206
|
# translates mgt port event into (enhanced) typed event
|
|
205
207
|
def enhanced_event_format(event)
|
|
206
208
|
return event.keys.each_with_object({}) do |e, h|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
209
|
+
new_name =
|
|
210
|
+
case e
|
|
211
|
+
when 'Elapsedusec' then 'elapsed_usec'
|
|
212
|
+
when 'Bytescont' then 'bytes_cont'
|
|
213
|
+
else e.capital_to_snake
|
|
214
|
+
end
|
|
215
|
+
h[new_name] =
|
|
216
|
+
if INTEGER_FIELDS.include?(e) then event[e].to_i
|
|
217
|
+
elsif BOOLEAN_FIELDS.include?(e) then event[e].eql?(BOOLEAN_TRUE)
|
|
218
|
+
else
|
|
219
|
+
event[e]
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# build command to send on management port
|
|
225
|
+
# @param data [Hash] {'type'=>'START','source'=>_path_,'destination'=>_path_}
|
|
226
|
+
def command_to_stream(data)
|
|
227
|
+
# TODO: translate enhanced to capitalized ?
|
|
228
|
+
data
|
|
229
|
+
.keys
|
|
230
|
+
.map{|k|"#{k.capitalize}: #{data[k]}"}
|
|
231
|
+
.unshift(MGT_HEADER)
|
|
232
|
+
.push('', '')
|
|
233
|
+
.join("\n")
|
|
213
234
|
end
|
|
214
235
|
end
|
|
215
236
|
|
data/lib/aspera/cli/formatter.rb
CHANGED
|
@@ -101,9 +101,10 @@ module Aspera
|
|
|
101
101
|
DISPLAY_FORMATS = %i[text nagios ruby json jsonpp yaml table csv image].freeze
|
|
102
102
|
# user output levels
|
|
103
103
|
DISPLAY_LEVELS = %i[info data error].freeze
|
|
104
|
-
|
|
104
|
+
# column names for single object display in table
|
|
105
|
+
SINGLE_OBJECT_COLUMN_NAMES = %i[field value].freeze
|
|
105
106
|
|
|
106
|
-
private_constant :
|
|
107
|
+
private_constant :FIELDS_LESS, :CSV_RECORD_SEPARATOR, :CSV_FIELD_SEPARATOR, :DISPLAY_FORMATS, :DISPLAY_LEVELS, :SINGLE_OBJECT_COLUMN_NAMES
|
|
107
108
|
# prefix to display error messages in user messages (terminal)
|
|
108
109
|
ERROR_FLASH = 'ERROR:'.bg_red.gray.blink.freeze
|
|
109
110
|
WARNING_FLASH = 'WARNING:'.bg_brown.black.blink.freeze
|
|
@@ -172,12 +173,37 @@ module Aspera
|
|
|
172
173
|
@spinner = nil
|
|
173
174
|
end
|
|
174
175
|
|
|
175
|
-
|
|
176
|
+
def declare_options(options)
|
|
177
|
+
default_table_style = if Environment.instance.terminal_supports_unicode?
|
|
178
|
+
{border: :unicode_round}
|
|
179
|
+
else
|
|
180
|
+
{}
|
|
181
|
+
end
|
|
182
|
+
options.declare(:format, 'Output format', values: DISPLAY_FORMATS, handler: {o: self, m: :option_handler}, default: :table)
|
|
183
|
+
options.declare(:output, 'Destination for results', types: String, handler: {o: self, m: :option_handler})
|
|
184
|
+
options.declare(:display, 'Output only some information', values: DISPLAY_LEVELS, handler: {o: self, m: :option_handler}, default: :info)
|
|
185
|
+
options.declare(
|
|
186
|
+
:fields, "Comma separated list of: fields, or #{SpecialValues::ALL}, or #{SpecialValues::DEF}", handler: {o: self, m: :option_handler},
|
|
187
|
+
types: [String, Array, Regexp, Proc],
|
|
188
|
+
default: SpecialValues::DEF)
|
|
189
|
+
options.declare(:select, 'Select only some items in lists: column, value', types: [Hash, Proc], handler: {o: self, m: :option_handler})
|
|
190
|
+
options.declare(:table_style, 'Table display style', types: [Hash], handler: {o: self, m: :option_handler}, default: default_table_style)
|
|
191
|
+
options.declare(:flat_hash, '(Table) Display deep values as additional keys', values: :bool, handler: {o: self, m: :option_handler}, default: true)
|
|
192
|
+
options.declare(
|
|
193
|
+
:multi_single, '(Table) Control how object list is displayed as single table, or multiple objects', values: %i[no yes single],
|
|
194
|
+
handler: {o: self, m: :option_handler}, default: :no)
|
|
195
|
+
options.declare(:show_secrets, 'Show secrets on command output', values: :bool, handler: {o: self, m: :option_handler}, default: false)
|
|
196
|
+
options.declare(:image, 'Options for image display', types: Hash, handler: {o: self, m: :option_handler}, default: {})
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# method accessed by option manager
|
|
200
|
+
# options are: format, output, display, fields, select, table_style, flat_hash, multi_single
|
|
176
201
|
def option_handler(option_symbol, operation, value=nil)
|
|
177
202
|
Aspera.assert_values(operation, %i[set get])
|
|
178
203
|
case operation
|
|
179
204
|
when :set
|
|
180
205
|
@options[option_symbol] = value
|
|
206
|
+
# special handling of some options
|
|
181
207
|
case option_symbol
|
|
182
208
|
when :output
|
|
183
209
|
$stdout = if value.eql?('-')
|
|
@@ -186,7 +212,9 @@ module Aspera
|
|
|
186
212
|
File.open(value, 'w')
|
|
187
213
|
end
|
|
188
214
|
when :image
|
|
215
|
+
# get list if key arguments of method
|
|
189
216
|
allowed_options = Preview::Terminal.method(:build).parameters.select{|i|i[0].eql?(:key)}.map{|i|i[1]}
|
|
217
|
+
# check that only supported options are given
|
|
190
218
|
unknown_options = value.keys.map(&:to_sym) - allowed_options
|
|
191
219
|
raise "Invalid parameter(s) for option image: #{unknown_options.join(', ')}, use #{allowed_options.join(', ')}" unless unknown_options.empty?
|
|
192
220
|
end
|
|
@@ -196,28 +224,6 @@ module Aspera
|
|
|
196
224
|
nil
|
|
197
225
|
end
|
|
198
226
|
|
|
199
|
-
def declare_options(options)
|
|
200
|
-
default_table_style = if Environment.instance.terminal_supports_unicode?
|
|
201
|
-
{border: :unicode_round}
|
|
202
|
-
else
|
|
203
|
-
{}
|
|
204
|
-
end
|
|
205
|
-
options.declare(:format, 'Output format', values: DISPLAY_FORMATS, handler: {o: self, m: :option_handler}, default: :table)
|
|
206
|
-
options.declare(:output, 'Destination for results', types: String, handler: {o: self, m: :option_handler})
|
|
207
|
-
options.declare(:display, 'Output only some information', values: DISPLAY_LEVELS, handler: {o: self, m: :option_handler}, default: :info)
|
|
208
|
-
options.declare(
|
|
209
|
-
:fields, "Comma separated list of: fields, or #{SpecialValues::ALL}, or #{SpecialValues::DEF}", handler: {o: self, m: :option_handler},
|
|
210
|
-
types: [String, Array, Regexp, Proc],
|
|
211
|
-
default: SpecialValues::DEF)
|
|
212
|
-
options.declare(:select, 'Select only some items in lists: column, value', types: [Hash, Proc], handler: {o: self, m: :option_handler})
|
|
213
|
-
options.declare(:table_style, 'Table display style', types: [Hash], handler: {o: self, m: :option_handler}, default: default_table_style)
|
|
214
|
-
options.declare(:flat_hash, '(Table) Display deep values as additional keys', values: :bool, handler: {o: self, m: :option_handler}, default: true)
|
|
215
|
-
options.declare(:transpose_single, '(Table) Single object fields output vertically', values: :bool, handler: {o: self, m: :option_handler}, default: true)
|
|
216
|
-
options.declare(:multi_table, '(Table) Each element of a table are displayed as a table', values: :bool, handler: {o: self, m: :option_handler}, default: false)
|
|
217
|
-
options.declare(:show_secrets, 'Show secrets on command output', values: :bool, handler: {o: self, m: :option_handler}, default: false)
|
|
218
|
-
options.declare(:image, 'Options for image display', types: Hash, handler: {o: self, m: :option_handler}, default: {})
|
|
219
|
-
end
|
|
220
|
-
|
|
221
227
|
# main output method
|
|
222
228
|
# data: for requested data, not displayed if level==error
|
|
223
229
|
# info: additional info, displayed if level==info
|
|
@@ -244,6 +250,118 @@ module Aspera
|
|
|
244
250
|
display_status(count_msg)
|
|
245
251
|
end
|
|
246
252
|
|
|
253
|
+
# this method displays the results, especially the table format
|
|
254
|
+
# @param type [Symbol] type of data
|
|
255
|
+
# @param data [Object] data to display
|
|
256
|
+
# @param total [Integer] total number of items
|
|
257
|
+
# @param fields [Array<String>] list of fields to display
|
|
258
|
+
# @param name [String] name of the column to display
|
|
259
|
+
def display_results(type:, data: nil, total: nil, fields: nil, name: nil)
|
|
260
|
+
Log.log.debug{"display_results: #{type} class=#{data.class}"}
|
|
261
|
+
Log.log.trace1{"display_results:data=#{data}"}
|
|
262
|
+
Aspera.assert_type(type, Symbol){'result must have type'}
|
|
263
|
+
Aspera.assert(!data.nil? || %i[empty nothing].include?(type)){'result must have data'}
|
|
264
|
+
display_item_count(data.length, total) unless total.nil?
|
|
265
|
+
SecretHider.deep_remove_secret(data) unless @options[:show_secrets] || @options[:display].eql?(:data)
|
|
266
|
+
case @options[:format]
|
|
267
|
+
when :text
|
|
268
|
+
display_message(:data, data.to_s)
|
|
269
|
+
when :nagios
|
|
270
|
+
Nagios.process(data)
|
|
271
|
+
when :ruby
|
|
272
|
+
display_message(:data, PP.pp(filter_list_on_fields(data), +''))
|
|
273
|
+
when :json
|
|
274
|
+
display_message(:data, JSON.generate(filter_list_on_fields(data)))
|
|
275
|
+
when :jsonpp
|
|
276
|
+
display_message(:data, JSON.pretty_generate(filter_list_on_fields(data)))
|
|
277
|
+
when :yaml
|
|
278
|
+
display_message(:data, YAML.dump(filter_list_on_fields(data)))
|
|
279
|
+
when :image
|
|
280
|
+
# assume it is an url
|
|
281
|
+
url = data
|
|
282
|
+
case type
|
|
283
|
+
when :single_object, :object_list
|
|
284
|
+
url = [url] if type.eql?(:single_object)
|
|
285
|
+
raise 'image display requires a single result' unless url.length == 1
|
|
286
|
+
fields = compute_fields(url, fields)
|
|
287
|
+
raise 'select a field to display' unless fields.length == 1
|
|
288
|
+
url = url.first
|
|
289
|
+
raise 'no such field' unless url.key?(fields.first)
|
|
290
|
+
url = url[fields.first]
|
|
291
|
+
end
|
|
292
|
+
raise "not url: #{url.class} #{url}" unless url.is_a?(String)
|
|
293
|
+
display_message(:data, status_image(url))
|
|
294
|
+
when :table, :csv
|
|
295
|
+
case type
|
|
296
|
+
when :config_over
|
|
297
|
+
display_table(Flattener.new(self).config_over(data), CONF_OVERVIEW_KEYS)
|
|
298
|
+
when :object_list, :single_object
|
|
299
|
+
obj_list = data
|
|
300
|
+
if type.eql?(:single_object)
|
|
301
|
+
obj_list = [obj_list]
|
|
302
|
+
@options[:multi_single] = :yes
|
|
303
|
+
end
|
|
304
|
+
Aspera.assert_type(obj_list, Array)
|
|
305
|
+
Aspera.assert(obj_list.all?(Hash)){"expecting Array of Hash: #{obj_list.inspect}"}
|
|
306
|
+
# :object_list is an array of hash tables, where key=colum name
|
|
307
|
+
obj_list = obj_list.map{|obj|Flattener.new(self).flatten(obj)} if @options[:flat_hash]
|
|
308
|
+
display_table(obj_list, compute_fields(obj_list, fields))
|
|
309
|
+
when :value_list
|
|
310
|
+
# :value_list is a simple array of values, name of column provided in the :name
|
|
311
|
+
display_table(data.map { |i| { name => i } }, [name])
|
|
312
|
+
when :empty # no table
|
|
313
|
+
display_message(:info, special_format('empty'))
|
|
314
|
+
return
|
|
315
|
+
when :nothing # no result expected
|
|
316
|
+
Log.log.debug('no result expected')
|
|
317
|
+
when :status # no table
|
|
318
|
+
# :status displays a simple message
|
|
319
|
+
display_message(:info, data)
|
|
320
|
+
when :text # no table
|
|
321
|
+
# :status displays a simple message
|
|
322
|
+
display_message(:data, data)
|
|
323
|
+
when :other_struct # no table
|
|
324
|
+
# :other_struct is any other type of structure
|
|
325
|
+
display_message(:data, PP.pp(data, +''))
|
|
326
|
+
else
|
|
327
|
+
raise "unknown data type: #{type}"
|
|
328
|
+
end
|
|
329
|
+
else
|
|
330
|
+
raise "not expected: #{@options[:format]}"
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
# @return text suitable to display an image from url
|
|
335
|
+
# # can be used in
|
|
336
|
+
def status_image(blob)
|
|
337
|
+
begin
|
|
338
|
+
raise URI::InvalidURIError, 'not uri' if !(blob =~ /\A#{URI::RFC2396_PARSER.make_regexp}\z/)
|
|
339
|
+
# it's a url
|
|
340
|
+
url = blob
|
|
341
|
+
unless Environment.instance.url_method.eql?(:text)
|
|
342
|
+
Environment.instance.open_uri(url)
|
|
343
|
+
return ''
|
|
344
|
+
end
|
|
345
|
+
# remote_image = Rest.new(base_url: url).read('')
|
|
346
|
+
# mime = remote_image[:http]['content-type']
|
|
347
|
+
# blob = remote_image[:http].body
|
|
348
|
+
# Log.log.warn("Image ? #{remote_image[:http]['content-type']}") unless mime.include?('image/')
|
|
349
|
+
blob = UriReader.read(url)
|
|
350
|
+
rescue URI::InvalidURIError
|
|
351
|
+
nil
|
|
352
|
+
end
|
|
353
|
+
# try base64
|
|
354
|
+
begin
|
|
355
|
+
blob = Base64.strict_decode64(blob)
|
|
356
|
+
rescue
|
|
357
|
+
nil
|
|
358
|
+
end
|
|
359
|
+
return Preview::Terminal.build(blob, **@options[:image].symbolize_keys)
|
|
360
|
+
end
|
|
361
|
+
#==========================================================================================
|
|
362
|
+
|
|
363
|
+
private
|
|
364
|
+
|
|
247
365
|
def all_fields(data)
|
|
248
366
|
data.each_with_object({}){|v, m|v.each_key{|c|m[c] = true}}.keys
|
|
249
367
|
end
|
|
@@ -314,9 +432,9 @@ module Aspera
|
|
|
314
432
|
end
|
|
315
433
|
end
|
|
316
434
|
|
|
317
|
-
#
|
|
318
|
-
# object_array
|
|
319
|
-
# fields
|
|
435
|
+
# displays a list of objects
|
|
436
|
+
# @param object_array [Array] array of hash
|
|
437
|
+
# @param fields [Array] list of column names
|
|
320
438
|
def display_table(object_array, fields)
|
|
321
439
|
Aspera.assert(!fields.nil?){'missing fields parameter'}
|
|
322
440
|
filter_columns_on_select(object_array)
|
|
@@ -330,13 +448,6 @@ module Aspera
|
|
|
330
448
|
display_message(:data, object_array.first[fields.first])
|
|
331
449
|
return
|
|
332
450
|
end
|
|
333
|
-
single_transposed = @options[:transpose_single] && object_array.length == 1
|
|
334
|
-
# Special case if only one row (it could be object_list or single_object)
|
|
335
|
-
if single_transposed
|
|
336
|
-
single = object_array.first
|
|
337
|
-
object_array = fields.map { |i| FIELD_VALUE_HEADINGS.zip([i, single[i]]).to_h }
|
|
338
|
-
fields = FIELD_VALUE_HEADINGS
|
|
339
|
-
end
|
|
340
451
|
Log.log.debug{Log.dump(:object_array, object_array)}
|
|
341
452
|
# convert data to string, and keep only display fields
|
|
342
453
|
final_table_rows = object_array.map { |r| fields.map { |c| r[c].to_s } }
|
|
@@ -345,11 +456,12 @@ module Aspera
|
|
|
345
456
|
# here : fields : list of column names
|
|
346
457
|
case @options[:format]
|
|
347
458
|
when :table
|
|
348
|
-
if @options[:
|
|
459
|
+
if @options[:multi_single].eql?(:yes) ||
|
|
460
|
+
(@options[:multi_single].eql?(:single) && final_table_rows.length.eql?(1))
|
|
349
461
|
final_table_rows.each do |row|
|
|
350
462
|
Log.log.debug{Log.dump(:row, row)}
|
|
351
463
|
display_message(:data, Terminal::Table.new(
|
|
352
|
-
headings:
|
|
464
|
+
headings: SINGLE_OBJECT_COLUMN_NAMES,
|
|
353
465
|
rows: fields.zip(row),
|
|
354
466
|
style: @options[:table_style]&.symbolize_keys))
|
|
355
467
|
end
|
|
@@ -366,110 +478,6 @@ module Aspera
|
|
|
366
478
|
raise "not expected: #{@options[:format]}"
|
|
367
479
|
end
|
|
368
480
|
end
|
|
369
|
-
|
|
370
|
-
# @return text suitable to display an image from url
|
|
371
|
-
def status_image(blob)
|
|
372
|
-
begin
|
|
373
|
-
raise URI::InvalidURIError, 'not uri' if !(blob =~ /\A#{URI::RFC2396_PARSER.make_regexp}\z/)
|
|
374
|
-
# it's a url
|
|
375
|
-
url = blob
|
|
376
|
-
unless Environment.instance.url_method.eql?(:text)
|
|
377
|
-
Environment.instance.open_uri(url)
|
|
378
|
-
return ''
|
|
379
|
-
end
|
|
380
|
-
# remote_image = Rest.new(base_url: url).read('')
|
|
381
|
-
# mime = remote_image[:http]['content-type']
|
|
382
|
-
# blob = remote_image[:http].body
|
|
383
|
-
# Log.log.warn("Image ? #{remote_image[:http]['content-type']}") unless mime.include?('image/')
|
|
384
|
-
blob = UriReader.read(url)
|
|
385
|
-
rescue URI::InvalidURIError
|
|
386
|
-
nil
|
|
387
|
-
end
|
|
388
|
-
# try base64
|
|
389
|
-
begin
|
|
390
|
-
blob = Base64.strict_decode64(blob)
|
|
391
|
-
rescue
|
|
392
|
-
nil
|
|
393
|
-
end
|
|
394
|
-
return Preview::Terminal.build(blob, **@options[:image].symbolize_keys)
|
|
395
|
-
end
|
|
396
|
-
|
|
397
|
-
# this method displays the results, especially the table format
|
|
398
|
-
# @param type [Symbol] type of data
|
|
399
|
-
# @param data [Object] data to display
|
|
400
|
-
# @param total [Integer] total number of items
|
|
401
|
-
# @param fields [Array<String>] list of fields to display
|
|
402
|
-
# @param name [String] name of the column to display
|
|
403
|
-
def display_results(type:, data: nil, total: nil, fields: nil, name: nil)
|
|
404
|
-
Log.log.debug{"display_results: #{type} class=#{data.class} data=#{data}"}
|
|
405
|
-
Aspera.assert_type(type, Symbol){'result must have type'}
|
|
406
|
-
Aspera.assert(!data.nil? || %i[empty nothing].include?(type)){'result must have data'}
|
|
407
|
-
display_item_count(data.length, total) unless total.nil?
|
|
408
|
-
SecretHider.deep_remove_secret(data) unless @options[:show_secrets] || @options[:display].eql?(:data)
|
|
409
|
-
case @options[:format]
|
|
410
|
-
when :text
|
|
411
|
-
display_message(:data, data.to_s)
|
|
412
|
-
when :nagios
|
|
413
|
-
Nagios.process(data)
|
|
414
|
-
when :ruby
|
|
415
|
-
display_message(:data, PP.pp(filter_list_on_fields(data), +''))
|
|
416
|
-
when :json
|
|
417
|
-
display_message(:data, JSON.generate(filter_list_on_fields(data)))
|
|
418
|
-
when :jsonpp
|
|
419
|
-
display_message(:data, JSON.pretty_generate(filter_list_on_fields(data)))
|
|
420
|
-
when :yaml
|
|
421
|
-
display_message(:data, YAML.dump(filter_list_on_fields(data)))
|
|
422
|
-
when :image
|
|
423
|
-
# assume it is an url
|
|
424
|
-
url = data
|
|
425
|
-
case type
|
|
426
|
-
when :single_object, :object_list
|
|
427
|
-
url = [url] if type.eql?(:single_object)
|
|
428
|
-
raise 'image display requires a single result' unless url.length == 1
|
|
429
|
-
fields = compute_fields(url, fields)
|
|
430
|
-
raise 'select a field to display' unless fields.length == 1
|
|
431
|
-
url = url.first
|
|
432
|
-
raise 'no such field' unless url.key?(fields.first)
|
|
433
|
-
url = url[fields.first]
|
|
434
|
-
end
|
|
435
|
-
raise "not url: #{url.class} #{url}" unless url.is_a?(String)
|
|
436
|
-
display_message(:data, status_image(url))
|
|
437
|
-
when :table, :csv
|
|
438
|
-
case type
|
|
439
|
-
when :config_over
|
|
440
|
-
display_table(Flattener.new(self).config_over(data), CONF_OVERVIEW_KEYS)
|
|
441
|
-
when :object_list, :single_object
|
|
442
|
-
obj_list = data
|
|
443
|
-
obj_list = [obj_list] if type.eql?(:single_object)
|
|
444
|
-
Aspera.assert_type(obj_list, Array)
|
|
445
|
-
Aspera.assert(obj_list.all?(Hash)){"expecting Array of Hash: #{obj_list.inspect}"}
|
|
446
|
-
# :object_list is an array of hash tables, where key=colum name
|
|
447
|
-
obj_list = obj_list.map{|obj|Flattener.new(self).flatten(obj)} if @options[:flat_hash]
|
|
448
|
-
display_table(obj_list, compute_fields(obj_list, fields))
|
|
449
|
-
when :value_list
|
|
450
|
-
# :value_list is a simple array of values, name of column provided in the :name
|
|
451
|
-
display_table(data.map { |i| { name => i } }, [name])
|
|
452
|
-
when :empty # no table
|
|
453
|
-
display_message(:info, special_format('empty'))
|
|
454
|
-
return
|
|
455
|
-
when :nothing # no result expected
|
|
456
|
-
Log.log.debug('no result expected')
|
|
457
|
-
when :status # no table
|
|
458
|
-
# :status displays a simple message
|
|
459
|
-
display_message(:info, data)
|
|
460
|
-
when :text # no table
|
|
461
|
-
# :status displays a simple message
|
|
462
|
-
display_message(:data, data)
|
|
463
|
-
when :other_struct # no table
|
|
464
|
-
# :other_struct is any other type of structure
|
|
465
|
-
display_message(:data, PP.pp(data, +''))
|
|
466
|
-
else
|
|
467
|
-
raise "unknown data type: #{type}"
|
|
468
|
-
end
|
|
469
|
-
else
|
|
470
|
-
raise "not expected: #{@options[:format]}"
|
|
471
|
-
end
|
|
472
|
-
end
|
|
473
481
|
end
|
|
474
482
|
end
|
|
475
483
|
end
|
data/lib/aspera/cli/info.rb
CHANGED
|
@@ -12,7 +12,7 @@ module Aspera
|
|
|
12
12
|
SRC_URL = 'https://github.com/IBM/aspera-cli'
|
|
13
13
|
# set this to warn in advance when minimum required ruby version will increase
|
|
14
14
|
# see also required_ruby_version in gemspec file
|
|
15
|
-
RUBY_FUTURE_MINIMUM_VERSION = '3.
|
|
15
|
+
RUBY_FUTURE_MINIMUM_VERSION = '3.1'
|
|
16
16
|
end
|
|
17
17
|
end
|
|
18
18
|
end
|
data/lib/aspera/cli/manager.rb
CHANGED
|
@@ -105,6 +105,7 @@ module Aspera
|
|
|
105
105
|
# @param descr [String] description for help
|
|
106
106
|
# @param to_check [Object] value to check
|
|
107
107
|
# @param type_list [NilClass, Class, Array[Class]] accepted value type(s)
|
|
108
|
+
# @param check_array [bool] set to true if it is a list of values to check
|
|
108
109
|
def validate_type(what, descr, to_check, type_list, check_array: false)
|
|
109
110
|
return nil if type_list.nil?
|
|
110
111
|
Aspera.assert(type_list.is_a?(Array) && type_list.all?(Class)){'types must be a Class Array'}
|