aspera-cli 4.26.2 → 4.27.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +310 -21
- data/CONTRIBUTING.md +99 -14
- data/TODO.md +50 -0
- data/bin/ascli +3 -3
- data/docs/README.md +10977 -0
- data/docs/test-mcp-with-ai.md +202 -0
- data/lib/aspera/agent/base.rb +29 -7
- data/lib/aspera/agent/connect.rb +58 -21
- data/lib/aspera/agent/desktop.rb +53 -18
- data/lib/aspera/agent/direct.rb +71 -40
- data/lib/aspera/agent/factory.rb +3 -3
- data/lib/aspera/agent/httpgw.rb +55 -14
- data/lib/aspera/agent/node.rb +34 -2
- data/lib/aspera/agent/transferd.rb +48 -12
- data/lib/aspera/api/alee.rb +1 -1
- data/lib/aspera/api/aoc.rb +42 -44
- data/lib/aspera/api/cos_node.rb +10 -9
- data/lib/aspera/api/faspex.rb +16 -15
- data/lib/aspera/api/httpgw.rb +23 -23
- data/lib/aspera/api/node.rb +51 -70
- data/lib/aspera/api/queries/bss_subscription_account.graphql +18 -0
- data/lib/aspera/api/queries/bss_subscription_usage.graphql +18 -0
- data/lib/aspera/ascmd.rb +32 -30
- data/lib/aspera/ascp/installation.rb +75 -69
- data/lib/aspera/ascp/management.rb +7 -8
- data/lib/aspera/assert.rb +12 -13
- data/lib/aspera/cli/ascp_actions.rb +155 -0
- data/lib/aspera/cli/async_transfer_store.rb +125 -0
- data/lib/aspera/cli/bootstrapper.rb +197 -0
- data/lib/aspera/cli/command_registry.rb +145 -0
- data/lib/aspera/cli/command_spec.rb +141 -0
- data/lib/aspera/cli/context.rb +19 -6
- data/lib/aspera/cli/error.rb +16 -0
- data/lib/aspera/cli/extended_value.rb +27 -26
- data/lib/aspera/cli/formatter.rb +95 -59
- data/lib/aspera/cli/gem_checker.rb +65 -0
- data/lib/aspera/cli/hints.rb +3 -3
- data/lib/aspera/cli/http.rb +74 -30
- data/lib/aspera/cli/info.rb +2 -0
- data/lib/aspera/cli/mailer.rb +97 -0
- data/lib/aspera/cli/mcp_tool.rb +225 -0
- data/lib/aspera/cli/option_declarator.rb +77 -0
- data/lib/aspera/cli/options.schema.yaml +634 -7
- data/lib/aspera/cli/parser.rb +1217 -0
- data/lib/aspera/cli/plugins/alee.rb +20 -22
- data/lib/aspera/cli/plugins/aoc.rb +1178 -881
- data/lib/aspera/cli/plugins/ats.rb +205 -162
- data/lib/aspera/cli/plugins/base.rb +703 -175
- data/lib/aspera/cli/plugins/basic_auth.rb +7 -9
- data/lib/aspera/cli/plugins/config.rb +434 -757
- data/lib/aspera/cli/plugins/console.rb +107 -65
- data/lib/aspera/cli/plugins/cos.rb +46 -33
- data/lib/aspera/cli/plugins/factory.rb +6 -6
- data/lib/aspera/cli/plugins/faspex5.rb +627 -388
- data/lib/aspera/cli/plugins/faspio.rb +54 -51
- data/lib/aspera/cli/plugins/httpgw.rb +18 -25
- data/lib/aspera/cli/plugins/mcp.rb +279 -0
- data/lib/aspera/cli/plugins/node.rb +988 -840
- data/lib/aspera/cli/plugins/oauth.rb +7 -10
- data/lib/aspera/cli/plugins/orchestrator.rb +112 -135
- data/lib/aspera/cli/plugins/preview.rb +195 -157
- data/lib/aspera/cli/plugins/server.rb +141 -91
- data/lib/aspera/cli/plugins/shares.rb +343 -110
- data/lib/aspera/cli/preset_actions.rb +159 -0
- data/lib/aspera/cli/preset_manager.rb +82 -39
- data/lib/aspera/cli/result.rb +78 -26
- data/lib/aspera/cli/runner.rb +323 -153
- data/lib/aspera/cli/secret_finder.rb +40 -0
- data/lib/aspera/cli/special_values.rb +1 -0
- data/lib/aspera/cli/sync_actions.rb +89 -64
- data/lib/aspera/cli/terminal_formatter.rb +3 -3
- data/lib/aspera/cli/transfer_actions.rb +90 -0
- data/lib/aspera/cli/transfer_agent.rb +129 -64
- data/lib/aspera/cli/transfer_progress.rb +9 -9
- data/lib/aspera/cli/vault_manager.rb +79 -0
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/cli/wizard.rb +28 -30
- data/lib/aspera/colors.rb +3 -3
- data/lib/aspera/command_line_builder.rb +34 -34
- data/lib/aspera/command_line_converter.rb +1 -1
- data/lib/aspera/coverage.rb +1 -2
- data/lib/aspera/data_repository.rb +2 -1
- data/lib/aspera/dot_container.rb +12 -12
- data/lib/aspera/environment.rb +30 -23
- data/lib/aspera/exec_spec.rb +13 -0
- data/lib/aspera/faspex_gw.rb +5 -5
- data/lib/aspera/faspex_postproc.rb +16 -10
- data/lib/aspera/graphql.rb +37 -0
- data/lib/aspera/hash_ext.rb +8 -2
- data/lib/aspera/json_rpc/client.rb +62 -0
- data/lib/aspera/json_rpc/version.rb +7 -0
- data/lib/aspera/keychain/base.rb +10 -3
- data/lib/aspera/keychain/encrypted_hash.rb +92 -17
- data/lib/aspera/keychain/factory.rb +20 -8
- data/lib/aspera/keychain/hashicorp_vault.rb +1 -1
- data/lib/aspera/keychain/macos_security.rb +23 -25
- data/lib/aspera/keychain/one_password_api.rb +86 -0
- data/lib/aspera/keychain/one_password_base.rb +34 -0
- data/lib/aspera/keychain/one_password_cli.rb +98 -0
- data/lib/aspera/link_header.rb +82 -0
- data/lib/aspera/log.rb +29 -15
- data/lib/aspera/markdown.rb +90 -7
- data/lib/aspera/nagios.rb +8 -8
- data/lib/aspera/node_simulator.rb +35 -27
- data/lib/aspera/oauth/base.rb +9 -10
- data/lib/aspera/oauth/boot.rb +5 -5
- data/lib/aspera/oauth/factory.rb +12 -10
- data/lib/aspera/oauth/jwt.rb +9 -11
- data/lib/aspera/oauth/web.rb +6 -6
- data/lib/aspera/persistency_action_once.rb +12 -12
- data/lib/aspera/persistency_folder.rb +18 -11
- data/lib/aspera/preview/file_types.rb +5 -5
- data/lib/aspera/preview/generator.rb +52 -49
- data/lib/aspera/preview/options.rb +3 -2
- data/lib/aspera/preview/terminal.rb +5 -5
- data/lib/aspera/preview/utils.rb +66 -19
- data/lib/aspera/products/connect.rb +2 -2
- data/lib/aspera/products/desktop.rb +1 -1
- data/lib/aspera/products/other.rb +3 -3
- data/lib/aspera/products/transferd.rb +4 -4
- data/lib/aspera/proxy_auto_config.rb +15 -14
- data/lib/aspera/rest.rb +159 -151
- data/lib/aspera/rest_error_analyzer.rb +6 -6
- data/lib/aspera/rest_errors_aspera.rb +0 -10
- data/lib/aspera/rest_list.rb +11 -10
- data/lib/aspera/schema/IBM Aspera Node API-4.4.6.yaml +6232 -0
- data/lib/aspera/schema/IBM Aspera faspio Gateway API-1.0.0.yaml +249 -0
- data/lib/aspera/schema/IBM Aspera on Cloud API-0.2.6-enhanced.yaml +1853 -510
- data/lib/aspera/schema/IBM_Aspera_Shares.yaml +4653 -0
- data/lib/aspera/schema/documentation.rb +65 -28
- data/lib/aspera/schema/reader.rb +136 -13
- data/lib/aspera/schema/registry.rb +60 -8
- data/lib/aspera/secret_hider.rb +12 -7
- data/lib/aspera/ssh.rb +64 -31
- data/lib/aspera/ssl.rb +5 -5
- data/lib/aspera/sync/conf.schema.yaml +2 -2
- data/lib/aspera/sync/database.rb +2 -2
- data/lib/aspera/sync/operations.rb +22 -25
- data/lib/aspera/temp_file_manager.rb +25 -5
- data/lib/aspera/timer_limiter.rb +1 -1
- data/lib/aspera/transfer/faux_file.rb +24 -11
- data/lib/aspera/transfer/parameters.rb +36 -34
- data/lib/aspera/transfer/result.rb +74 -0
- data/lib/aspera/transfer/resumer.rb +10 -10
- data/lib/aspera/transfer/spec.rb +18 -1
- data/lib/aspera/transfer/spec.schema.yaml +12 -3
- data/lib/aspera/uri_reader.rb +52 -15
- data/lib/aspera/web_auth.rb +8 -7
- data/lib/aspera/web_server_simple.rb +15 -12
- data/lib/aspera/yaml.rb +5 -4
- data.tar.gz.sig +0 -0
- metadata +34 -6
- metadata.gz.sig +0 -0
- data/lib/aspera/cli/manager.rb +0 -766
- data/lib/aspera/cli/plugins/faspex.rb +0 -539
- data/lib/aspera/json_rpc.rb +0 -52
- data/lib/aspera/transfer/uri.rb +0 -56
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# cspell:ignore protobuf ckpt
|
|
4
|
+
require 'aspera/yaml'
|
|
4
5
|
require 'aspera/environment'
|
|
5
6
|
require 'aspera/data_repository'
|
|
6
7
|
require 'aspera/log'
|
|
@@ -46,9 +47,9 @@ module Aspera
|
|
|
46
47
|
def sdk_locations
|
|
47
48
|
location_url = @transferd_urls
|
|
48
49
|
transferd_locations = UriReader.read(location_url)
|
|
49
|
-
Log.log.debug{"Retrieving SDK locations from #{location_url}"}
|
|
50
|
+
Log.log.debug { "Retrieving SDK locations from #{location_url}" }
|
|
50
51
|
begin
|
|
51
|
-
return
|
|
52
|
+
return Yaml.safe_load(transferd_locations)
|
|
52
53
|
rescue Psych::SyntaxError
|
|
53
54
|
raise "Error when parsing yaml data from: #{location_url}"
|
|
54
55
|
end
|
|
@@ -60,23 +61,23 @@ module Aspera
|
|
|
60
61
|
# - "product:PRODUCT_NAME" to use ascp from named product
|
|
61
62
|
# - "product:FIRST" to use ascp from first found product
|
|
62
63
|
def sdk_folder=(ascp_location)
|
|
63
|
-
Aspera.assert_type(ascp_location, String){'ascp_location'}
|
|
64
|
+
Aspera.assert_type(ascp_location, String) { 'ascp_location' }
|
|
64
65
|
Aspera.assert(!ascp_location.empty?, 'ascp location cannot be empty: check your config file')
|
|
65
66
|
folder =
|
|
66
67
|
if ascp_location.start_with?(USE_PRODUCT_PREFIX)
|
|
67
68
|
product_name = ascp_location.delete_prefix(USE_PRODUCT_PREFIX)
|
|
68
69
|
if product_name.eql?(FIRST_FOUND)
|
|
69
70
|
pl = installed_products.first
|
|
70
|
-
|
|
71
|
+
Aspera.assert(!pl.nil?) { "No Aspera transfer module or SDK found.\nRefer to the manual or install SDK with command:\nascli conf transferd install" }
|
|
71
72
|
else
|
|
72
|
-
pl = installed_products.find{ |i| i[:name].eql?(product_name)}
|
|
73
|
-
|
|
73
|
+
pl = installed_products.find { |i| i[:name].eql?(product_name) }
|
|
74
|
+
Aspera.assert(!pl.nil?) { "No such product installed: #{product_name}" }
|
|
74
75
|
end
|
|
75
76
|
File.dirname(pl[:ascp_path])
|
|
76
77
|
else
|
|
77
78
|
ascp_location.include?('/ascp') ? File.dirname(ascp_location) : ascp_location
|
|
78
79
|
end
|
|
79
|
-
Log.log.debug{"ascp_folder=#{folder}"}
|
|
80
|
+
Log.log.debug { "ascp_folder=#{folder}" }
|
|
80
81
|
Products::Transferd.sdk_directory = folder
|
|
81
82
|
nil
|
|
82
83
|
end
|
|
@@ -88,14 +89,14 @@ module Aspera
|
|
|
88
89
|
# @return [Hash] with key = file name (String), and value = path to file
|
|
89
90
|
def file_paths
|
|
90
91
|
return SDK_FILES.to_h do |v|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
92
|
+
[v.to_s, begin
|
|
93
|
+
path(v)
|
|
94
|
+
rescue Errno::ENOENT => e
|
|
95
|
+
e.message.gsub(/.*assertion failed: /, '').gsub(/\): .*/, ')')
|
|
96
|
+
rescue => e
|
|
97
|
+
e.message
|
|
98
|
+
end]
|
|
99
|
+
end
|
|
99
100
|
end
|
|
100
101
|
|
|
101
102
|
# TODO: if using another product than SDK, should use files from there
|
|
@@ -106,23 +107,23 @@ module Aspera
|
|
|
106
107
|
|
|
107
108
|
# Get path of one resource file of currently activated product
|
|
108
109
|
# keys and certs are generated locally... (they are well known values, arch. independent)
|
|
109
|
-
# @param
|
|
110
|
+
# @param file_type [Symbol] key of the resource file
|
|
110
111
|
# @return [String, nil] Full path to the resource file or nil if not found
|
|
111
|
-
def path(
|
|
112
|
+
def path(file_type)
|
|
112
113
|
file_is_required = true
|
|
113
|
-
case
|
|
114
|
+
case file_type
|
|
114
115
|
when *EXE_FILES
|
|
115
|
-
file_is_required =
|
|
116
|
+
file_is_required = file_type.eql?(:ascp)
|
|
116
117
|
file = Products::Transferd.transferd_path
|
|
117
|
-
file = File.join(File.dirname(file), Environment.instance.exe_file(
|
|
118
|
+
file = File.join(File.dirname(file), Environment.instance.exe_file(file_type.to_s)) unless file_type.eql?(:transferd)
|
|
118
119
|
when :ssh_private_dsa, :ssh_private_rsa
|
|
119
120
|
# assume last 3 letters are type
|
|
120
|
-
type =
|
|
121
|
-
file = check_or_create_sdk_file("aspera_bypass_#{type}.pem"){DataRepository.instance.item(type)}
|
|
121
|
+
type = file_type.to_s[-3..].to_sym
|
|
122
|
+
file = check_or_create_sdk_file("aspera_bypass_#{type}.pem") { DataRepository.instance.item(type) }
|
|
122
123
|
when :aspera_license
|
|
123
|
-
file = check_or_create_sdk_file('aspera-license'){DataRepository.instance.item(:license)}
|
|
124
|
+
file = check_or_create_sdk_file('aspera-license') { DataRepository.instance.item(:license) }
|
|
124
125
|
when :aspera_conf
|
|
125
|
-
file = check_or_create_sdk_file('aspera.conf'){DEFAULT_ASPERA_CONF}
|
|
126
|
+
file = check_or_create_sdk_file('aspera.conf') { DEFAULT_ASPERA_CONF }
|
|
126
127
|
when :fallback_certificate, :fallback_private_key
|
|
127
128
|
file_key = File.join(Products::Transferd.sdk_directory, 'aspera_fallback_cert_private_key.pem')
|
|
128
129
|
file_cert = File.join(Products::Transferd.sdk_directory, 'aspera_fallback_cert.pem')
|
|
@@ -131,30 +132,31 @@ module Aspera
|
|
|
131
132
|
# create new self signed certificate for http fallback
|
|
132
133
|
private_key = OpenSSL::PKey::RSA.new(4096)
|
|
133
134
|
cert = WebServerSimple.self_signed_cert(private_key)
|
|
134
|
-
check_or_create_sdk_file('aspera_fallback_cert_private_key.pem', force: true){private_key.to_pem}
|
|
135
|
-
check_or_create_sdk_file('aspera_fallback_cert.pem', force: true){cert.to_pem}
|
|
135
|
+
check_or_create_sdk_file('aspera_fallback_cert_private_key.pem', force: true) { private_key.to_pem }
|
|
136
|
+
check_or_create_sdk_file('aspera_fallback_cert.pem', force: true) { cert.to_pem }
|
|
136
137
|
end
|
|
137
|
-
file =
|
|
138
|
-
else Aspera.error_unexpected_value(
|
|
138
|
+
file = file_type.eql?(:fallback_certificate) ? file_cert : file_key
|
|
139
|
+
else Aspera.error_unexpected_value(file_type)
|
|
139
140
|
end
|
|
140
141
|
return unless file_is_required || File.exist?(file)
|
|
141
|
-
Aspera.assert(File.exist?(file), type: Errno::ENOENT){"#{
|
|
142
|
+
Aspera.assert(File.exist?(file), type: Errno::ENOENT) { "#{file_type} not found (#{file})" }
|
|
142
143
|
return file
|
|
143
144
|
end
|
|
144
145
|
|
|
145
146
|
# default bypass key phrase
|
|
146
|
-
# @return [String]
|
|
147
|
+
# @return [String] UUID used as bypass key phrase for SSH certificate
|
|
147
148
|
def ssh_cert_uuid
|
|
148
149
|
return DataRepository.instance.item(:uuid)
|
|
149
150
|
end
|
|
150
151
|
|
|
151
152
|
# get paths of SSH keys to use for ascp client
|
|
152
|
-
# @param types [Symbol] types to use
|
|
153
|
+
# @param types [Symbol] types to use (one of CLIENT_SSH_KEY_OPTIONS)
|
|
154
|
+
# @return [Array<String>] list of private key file paths
|
|
153
155
|
def aspera_token_ssh_key_paths(types)
|
|
154
156
|
Aspera.assert_values(types, CLIENT_SSH_KEY_OPTIONS)
|
|
155
157
|
return case types
|
|
156
158
|
when :dsa_rsa, :rsa
|
|
157
|
-
types.to_s.split('_').map{ |i| Installation.instance.path("ssh_private_#{i}".to_sym)}
|
|
159
|
+
types.to_s.split('_').map { |i| Installation.instance.path("ssh_private_#{i}".to_sym) }
|
|
158
160
|
when :per_client
|
|
159
161
|
Aspera.error_not_implemented
|
|
160
162
|
end
|
|
@@ -170,13 +172,11 @@ module Aspera
|
|
|
170
172
|
Aspera.assert_type(exe_path, String)
|
|
171
173
|
Aspera.assert_type(vers_arg, String)
|
|
172
174
|
return unless File.exist?(exe_path)
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
raise "An error occurred when testing #{exe_path}: #{cmd_out}" unless $CHILD_STATUS == 0
|
|
175
|
+
cmd_out, _err, status = Environment.secure_execute(exe_path, vers_arg, mode: :capture, exception: false)
|
|
176
|
+
raise "An error occurred when testing #{exe_path}: #{cmd_out}" unless status.success?
|
|
176
177
|
# get version from ascp, only after full extract, as windows requires DLLs (SSL/TLS/etc...)
|
|
177
178
|
m = cmd_out.match(/ version ([0-9.]+)/)
|
|
178
|
-
|
|
179
|
-
return exe_version
|
|
179
|
+
m.nil? ? nil : m[1].gsub(/\.$/, '')
|
|
180
180
|
end
|
|
181
181
|
|
|
182
182
|
# Extract some stings from ascp logs
|
|
@@ -234,30 +234,30 @@ module Aspera
|
|
|
234
234
|
return ascp_data
|
|
235
235
|
end
|
|
236
236
|
|
|
237
|
-
# @return the url for download of SDK archive for the given platform and version
|
|
237
|
+
# @return [String] the url for download of SDK archive for the given platform and version
|
|
238
238
|
def sdk_url_for_platform(platform: nil, version: nil)
|
|
239
239
|
all_locations = sdk_locations
|
|
240
240
|
platform = Environment.instance.architecture if platform.nil?
|
|
241
|
-
locations = all_locations.select{ |l| l['platform'].eql?(platform)}
|
|
242
|
-
|
|
243
|
-
version = locations.max_by{ |entry| Gem::Version.new(entry['version'])}['version'] if version.nil?
|
|
244
|
-
info = locations.select{ |entry| entry['version'].eql?(version)}
|
|
245
|
-
|
|
241
|
+
locations = all_locations.select { |l| l['platform'].eql?(platform) }
|
|
242
|
+
Aspera.assert(!locations.empty?) { "No SDK for platform: #{platform}, available: #{all_locations.map { |i| i['platform'] }.uniq}" }
|
|
243
|
+
version = locations.max_by { |entry| Gem::Version.new(entry['version']) }['version'] if version.nil?
|
|
244
|
+
info = locations.select { |entry| entry['version'].eql?(version) }
|
|
245
|
+
Aspera.assert(!info.empty?) { "No such version: #{version} for #{platform}" }
|
|
246
246
|
return info.first['url']
|
|
247
247
|
end
|
|
248
248
|
|
|
249
|
-
# @param
|
|
249
|
+
# @param url [String] URL or filename used to detect archive format
|
|
250
|
+
# @param archive_io [StringIO] archive content as an in-memory IO stream
|
|
250
251
|
# @yieldparam entry_name [String] File path in archive
|
|
251
252
|
# @yieldparam entry_stream [IO, Gem::Package::TarReader::Entry] Data stream
|
|
252
253
|
# @yieldparam link_target [String, nil] Link target if symlink, nil otherwise
|
|
253
|
-
def extract_archive_files(
|
|
254
|
+
def extract_archive_files(url, archive_io)
|
|
254
255
|
Aspera.assert(block_given?, 'missing block')
|
|
255
|
-
case
|
|
256
|
+
case url
|
|
256
257
|
# Windows and Mac use zip
|
|
257
258
|
when /\.zip$/
|
|
258
259
|
require 'zip'
|
|
259
|
-
|
|
260
|
-
Zip::File.open(sdk_archive_path) do |zip_file|
|
|
260
|
+
Zip::File.open_buffer(archive_io) do |zip_file|
|
|
261
261
|
zip_file.each do |entry|
|
|
262
262
|
next if entry.name.end_with?('/')
|
|
263
263
|
entry.get_input_stream do |io|
|
|
@@ -269,7 +269,7 @@ module Aspera
|
|
|
269
269
|
when /\.tar\.gz/
|
|
270
270
|
require 'zlib'
|
|
271
271
|
require 'rubygems/package'
|
|
272
|
-
Zlib::GzipReader.
|
|
272
|
+
Zlib::GzipReader.wrap(archive_io) do |gzip|
|
|
273
273
|
Gem::Package::TarReader.new(gzip) do |tar|
|
|
274
274
|
tar.each do |entry|
|
|
275
275
|
next if entry.directory?
|
|
@@ -278,19 +278,18 @@ module Aspera
|
|
|
278
278
|
end
|
|
279
279
|
end
|
|
280
280
|
else
|
|
281
|
-
raise "unknown archive extension: #{
|
|
281
|
+
raise "unknown archive extension: #{url}"
|
|
282
282
|
end
|
|
283
283
|
end
|
|
284
284
|
|
|
285
|
-
#
|
|
285
|
+
# Downloads and extracts the SDK archive for the current platform into +folder+
|
|
286
286
|
# @param folder [String] Destination folder path
|
|
287
287
|
# @param url [nil, String] URL to SDK archive, if nil: default url for version
|
|
288
288
|
# @param version [nil, String] Specific version, if nil: latest version
|
|
289
289
|
# @param backup [Boolean] If destination folder exists, then rename
|
|
290
290
|
# @yieldparam entry_name [String] File path from archive
|
|
291
291
|
# @yieldreturn [String, nil] Destination sub folder (end with /) or file, or nil to not extract
|
|
292
|
-
|
|
293
|
-
def install_sdk(folder:, url: nil, version: nil, backup: true)
|
|
292
|
+
def download_sdk(folder:, url: nil, version: nil, backup: true)
|
|
294
293
|
url ||= sdk_url_for_platform(version: version)
|
|
295
294
|
# Rename old install
|
|
296
295
|
if backup && Dir.exist?(folder) && !Dir.empty?(folder)
|
|
@@ -303,13 +302,19 @@ module Aspera
|
|
|
303
302
|
extracted_files = {}
|
|
304
303
|
# Security: Get canonical path of installation directory for boundary checks
|
|
305
304
|
install_boundary = File.realpath(folder)
|
|
306
|
-
|
|
307
|
-
|
|
305
|
+
archive_io = StringIO.new
|
|
306
|
+
if UriReader.file?(url)
|
|
307
|
+
archive_io.write(File.binread(UriReader.file_path(url)))
|
|
308
|
+
else
|
|
309
|
+
Rest.new(base_url: url, redirect_max: 3).call(operation: 'GET', save_to: archive_io)
|
|
310
|
+
archive_io.rewind
|
|
311
|
+
end
|
|
312
|
+
extract_archive_files(url, archive_io) do |entry_name, entry_stream, link_target|
|
|
308
313
|
dest_folder = if block_given?
|
|
309
314
|
yield(entry_name)
|
|
310
315
|
else
|
|
311
316
|
# default files to extract directly to main folder if in selected source folders
|
|
312
|
-
Products::Transferd::RUNTIME_FOLDERS.any?{ |i| entry_name.match?(%r{^[^/]*/#{i}/})} ? '/' : nil
|
|
317
|
+
Products::Transferd::RUNTIME_FOLDERS.any? { |i| entry_name.match?(%r{^[^/]*/#{i}/}) } ? '/' : nil
|
|
313
318
|
end
|
|
314
319
|
next if dest_folder.nil?
|
|
315
320
|
dest_folder = File.join(folder, dest_folder)
|
|
@@ -322,7 +327,7 @@ module Aspera
|
|
|
322
327
|
# Security: Detect basename collisions that could overwrite symlinks
|
|
323
328
|
file_basename = File.basename(dest_file)
|
|
324
329
|
if extracted_files.key?(file_basename)
|
|
325
|
-
Log.log.warn{"Rejecting file with duplicate basename: #{entry_name} (basename: #{file_basename}, previous: #{extracted_files[file_basename]})"}
|
|
330
|
+
Log.log.warn { "Rejecting file with duplicate basename: #{entry_name} (basename: #{file_basename}, previous: #{extracted_files[file_basename]})" }
|
|
326
331
|
next
|
|
327
332
|
end
|
|
328
333
|
extracted_files[file_basename] = entry_name
|
|
@@ -330,7 +335,7 @@ module Aspera
|
|
|
330
335
|
if link_target.nil?
|
|
331
336
|
# Security: Check if destination already exists
|
|
332
337
|
if File.exist?(dest_file)
|
|
333
|
-
Log.log.warn{"Rejecting write to existing file or link: #{dest_file}"}
|
|
338
|
+
Log.log.warn { "Rejecting write to existing file or link: #{dest_file}" }
|
|
334
339
|
next
|
|
335
340
|
end
|
|
336
341
|
# Security: Verify the resolved path stays within installation boundary
|
|
@@ -340,17 +345,17 @@ module Aspera
|
|
|
340
345
|
# Check where the file would resolve to (handles existing symlinks in path)
|
|
341
346
|
resolved_dest = File.realpath(File.dirname(dest_file))
|
|
342
347
|
unless resolved_dest.start_with?(install_boundary)
|
|
343
|
-
Log.log.warn{"Rejecting file outside installation directory: #{dest_file} resolves to #{resolved_dest}"}
|
|
348
|
+
Log.log.warn { "Rejecting file outside installation directory: #{dest_file} resolves to #{resolved_dest}" }
|
|
344
349
|
next
|
|
345
350
|
end
|
|
346
351
|
rescue Errno::ENOENT
|
|
347
352
|
# Directory doesn't exist yet, verify the intended path
|
|
348
353
|
unless dest_file.start_with?(folder)
|
|
349
|
-
Log.log.warn{"Rejecting file with path outside installation directory: #{dest_file}"}
|
|
354
|
+
Log.log.warn { "Rejecting file with path outside installation directory: #{dest_file}" }
|
|
350
355
|
next
|
|
351
356
|
end
|
|
352
357
|
end
|
|
353
|
-
File.open(dest_file, 'wb'){ |output_stream| IO.copy_stream(entry_stream, output_stream)}
|
|
358
|
+
File.open(dest_file, 'wb') { |output_stream| IO.copy_stream(entry_stream, output_stream) }
|
|
354
359
|
else
|
|
355
360
|
# Security: Validate symlink target stays within installation boundary
|
|
356
361
|
# Resolve the symlink target relative to its location
|
|
@@ -364,7 +369,7 @@ module Aspera
|
|
|
364
369
|
end
|
|
365
370
|
# Check if resolved target would be outside installation directory
|
|
366
371
|
unless resolved_target.start_with?(install_boundary)
|
|
367
|
-
Log.log.warn{"Rejecting symlink pointing outside installation directory: #{entry_name} -> #{link_target} (resolves to #{resolved_target})"}
|
|
372
|
+
Log.log.warn { "Rejecting symlink pointing outside installation directory: #{entry_name} -> #{link_target} (resolves to #{resolved_target})" }
|
|
368
373
|
next
|
|
369
374
|
end
|
|
370
375
|
File.symlink(link_target, dest_file)
|
|
@@ -372,12 +377,13 @@ module Aspera
|
|
|
372
377
|
end
|
|
373
378
|
end
|
|
374
379
|
|
|
375
|
-
#
|
|
376
|
-
# @param url
|
|
377
|
-
# @param version [nil, String]
|
|
378
|
-
|
|
380
|
+
# Downloads, installs and configures the SDK, returns name, version and install folder
|
|
381
|
+
# @param url [nil, String] URL to SDK archive, if nil: default url for version
|
|
382
|
+
# @param version [nil, String] Specific version, if nil: latest version
|
|
383
|
+
# @return [Array<String>] sdk name, sdk version, install folder
|
|
384
|
+
def install_sdk(url: nil, version: nil)
|
|
379
385
|
folder = Products::Transferd.sdk_directory
|
|
380
|
-
|
|
386
|
+
download_sdk(folder: folder, url: url, version: version)
|
|
381
387
|
# Ensure necessary files are there, or generate them, restrict file access on SDK executables
|
|
382
388
|
SDK_FILES.each do |file_id_sym|
|
|
383
389
|
file_path = path(file_id_sym)
|
|
@@ -420,7 +426,7 @@ module Aspera
|
|
|
420
426
|
# all executable files from SDK
|
|
421
427
|
EXE_FILES = %i[ascp ascp4 async transferd].freeze
|
|
422
428
|
# IDs of files present in SDK
|
|
423
|
-
SDK_FILES = %i[ssh_private_dsa ssh_private_rsa aspera_license aspera_conf fallback_certificate fallback_private_key]
|
|
429
|
+
SDK_FILES = (EXE_FILES + %i[ssh_private_dsa ssh_private_rsa aspera_license aspera_conf fallback_certificate fallback_private_key]).freeze
|
|
424
430
|
TRANSFERD_ARCHIVE_LOCATION_URL = 'https://ibm.biz/sdk_location'
|
|
425
431
|
# filename for ascp with optional extension (Windows)
|
|
426
432
|
private_constant :DEFAULT_ASPERA_CONF, :EXE_FILES, :SDK_FILES, :TRANSFERD_ARCHIVE_LOCATION_URL
|
|
@@ -433,7 +439,7 @@ module Aspera
|
|
|
433
439
|
|
|
434
440
|
public
|
|
435
441
|
|
|
436
|
-
# @return the list of installed products in format of product_locations_on_current_os
|
|
442
|
+
# @return [Array<Hash>] the list of installed products in format of product_locations_on_current_os
|
|
437
443
|
def installed_products
|
|
438
444
|
return @found_products unless @found_products.nil?
|
|
439
445
|
# :expected M app name is taken from the manifest if present, else defaults to this value
|
|
@@ -12,7 +12,7 @@ module Aspera
|
|
|
12
12
|
# https://www.ibm.com/support/pages/error-code-reference-tables
|
|
13
13
|
# mgmtmess.c : as_mgmt_err_is_retryable
|
|
14
14
|
# Note that the fact that an error is retry-able is not internally defined by protocol, it's client-side responsibility
|
|
15
|
-
# rubocop:disable Layout/FirstHashElementLineBreak
|
|
15
|
+
# rubocop:disable-next Layout/FirstHashElementLineBreak
|
|
16
16
|
ERRORS = {
|
|
17
17
|
# id retry-able mnemo/code message additional info
|
|
18
18
|
0 => {r: false, c: 'UNKNOWN', m: 'unknown', a: 'unknown'},
|
|
@@ -93,7 +93,6 @@ module Aspera
|
|
|
93
93
|
66 => {r: false, c: 'PEER_REQUIRES_FIPS', m: 'Peer rejects cipher due to FIPS mode enabled on peer',
|
|
94
94
|
a: 'Peer rejects cipher due to FIPS mode enabled on peer'}
|
|
95
95
|
}.freeze
|
|
96
|
-
# rubocop:enable Layout/FirstHashElementLineBreak
|
|
97
96
|
# cspell: disable
|
|
98
97
|
OPERATIONS = %w[
|
|
99
98
|
NOP
|
|
@@ -306,8 +305,8 @@ module Aspera
|
|
|
306
305
|
# @param name [String] Field name
|
|
307
306
|
def field_snake_to_native(name)
|
|
308
307
|
field = name.delete('_')
|
|
309
|
-
result = PARAMETERS.find{ |w| w.casecmp?(field)}
|
|
310
|
-
|
|
308
|
+
result = PARAMETERS.find { |w| w.casecmp?(field) }
|
|
309
|
+
Aspera.assert(!result.nil?) { "No such field: #{name}" }
|
|
311
310
|
result
|
|
312
311
|
end
|
|
313
312
|
|
|
@@ -328,7 +327,7 @@ module Aspera
|
|
|
328
327
|
# @return [String] frame to send on management port
|
|
329
328
|
def command_to_stream(data)
|
|
330
329
|
data
|
|
331
|
-
.map{ |key, value| "#{field_snake_to_native(key)}: #{value}"}
|
|
330
|
+
.map { |key, value| "#{field_snake_to_native(key)}: #{value}" }
|
|
332
331
|
.unshift(MGT_HEADER)
|
|
333
332
|
.push(MGT_FRAME_SEPARATOR, '')
|
|
334
333
|
.join("\n")
|
|
@@ -353,15 +352,15 @@ module Aspera
|
|
|
353
352
|
# begin event
|
|
354
353
|
@event_build = {}
|
|
355
354
|
when /^([^:]+): (.*)$/
|
|
356
|
-
Aspera.assert_type(@event_build, Hash){'mgt port: unexpected line: data without header'}
|
|
355
|
+
Aspera.assert_type(@event_build, Hash) { 'mgt port: unexpected line: data without header' }
|
|
357
356
|
# event field
|
|
358
357
|
@event_build[Regexp.last_match(1)] = Regexp.last_match(2)
|
|
359
358
|
when MGT_FRAME_SEPARATOR
|
|
360
|
-
Aspera.assert_type(@event_build, Hash){'mgt port: unexpected line: end frame without header'}
|
|
359
|
+
Aspera.assert_type(@event_build, Hash) { 'mgt port: unexpected line: end frame without header' }
|
|
361
360
|
@last_event = @event_build
|
|
362
361
|
@event_build = nil
|
|
363
362
|
return @last_event
|
|
364
|
-
else Aspera.error_unexpected_value(line){'mgt port'}
|
|
363
|
+
else Aspera.error_unexpected_value(line) { 'mgt port' }
|
|
365
364
|
end
|
|
366
365
|
return
|
|
367
366
|
end
|
data/lib/aspera/assert.rb
CHANGED
|
@@ -34,9 +34,9 @@ module Aspera
|
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
# Assert that a condition is true, else raise exception
|
|
37
|
-
# @param assertion [
|
|
38
|
-
# @param info
|
|
39
|
-
# @param type
|
|
37
|
+
# @param assertion [Boolean] Must be true
|
|
38
|
+
# @param info [String,nil] Fixed message in case assert fails, else use block
|
|
39
|
+
# @param type [Exception,Symbol] Exception to raise, or Symbol for Log.log level
|
|
40
40
|
# @yieldreturn [String] A string that describes the problem for complex messages
|
|
41
41
|
# The block is executed in the context of the Aspera module
|
|
42
42
|
def assert(assertion, info = nil, type: AssertError)
|
|
@@ -45,7 +45,6 @@ module Aspera
|
|
|
45
45
|
message = 'assertion failed'
|
|
46
46
|
info = yield if block_given?
|
|
47
47
|
message = type.eql?(AssertError) ? "#{message}: #{info}" : info if info
|
|
48
|
-
# message = "#{message}: #{caller.find{ |call| !call.start_with?(__FILE__)}}"
|
|
49
48
|
report_error(type, message)
|
|
50
49
|
end
|
|
51
50
|
|
|
@@ -55,7 +54,7 @@ module Aspera
|
|
|
55
54
|
# @param type [Exception,Symbol] Exception to raise, or Symbol for Log.log
|
|
56
55
|
# @yieldreturn [String] Additional description to prepend to the error message
|
|
57
56
|
def assert_type(value, *classes, type: AssertError)
|
|
58
|
-
assert(classes.any?{ |k| value.is_a?(k)}, type: type){"#{"#{yield}: " if block_given?}expecting type #{classes.join(', ')}, but have (#{value.class})#{value.inspect}"}
|
|
57
|
+
assert(classes.any? { |k| value.is_a?(k) }, type: type) { "#{"#{yield}: " if block_given?}expecting type #{classes.join(', ')}, but have (#{value.class})#{value.inspect}" }
|
|
59
58
|
end
|
|
60
59
|
|
|
61
60
|
# Assert that all value of array are of the same specified type.
|
|
@@ -64,20 +63,20 @@ module Aspera
|
|
|
64
63
|
# @param type [Exception,Symbol] Exception to raise, or Symbol for Log.log
|
|
65
64
|
# @yieldreturn [String] Additional description to prepend to the error message
|
|
66
65
|
def assert_array_all(array, klass, type: AssertError)
|
|
67
|
-
assert_type(array, Array, type: AssertError){'array'}
|
|
68
|
-
assert(array.all?(klass), type: type){"#{"#{yield}: " if block_given?}expecting all as #{klass}, but have #{array.map(&:class).uniq}"}
|
|
66
|
+
assert_type(array, Array, type: AssertError) { 'array' }
|
|
67
|
+
assert(array.all?(klass), type: type) { "#{"#{yield}: " if block_given?}expecting all as #{klass}, but have #{array.map(&:class).uniq}" }
|
|
69
68
|
end
|
|
70
69
|
|
|
71
70
|
# Assert value is Hash, keys have type, and Values have type
|
|
72
71
|
# @param hash [Hash] The hash to check
|
|
73
|
-
# @param key_class [Class] The expected type of keys (or nil)
|
|
74
|
-
# @param value_class [Class] The expected type of values (or nil)
|
|
72
|
+
# @param key_class [Class] The expected type of keys (or nil for no check)
|
|
73
|
+
# @param value_class [Class] The expected type of values (or nil for no check)
|
|
75
74
|
# @param type [Exception,Symbol] Exception to raise, or Symbol for Log.log
|
|
76
75
|
# @yieldreturn [String] Additional description to prepend to the error message
|
|
77
76
|
def assert_hash_all(hash, key_class, value_class, type: AssertError)
|
|
78
|
-
assert_type(hash, Hash, type: AssertError){'hash'}
|
|
79
|
-
assert_array_all(hash.keys, key_class, type: type){"#{"#{yield}: " if block_given?}keys"} unless key_class.nil?
|
|
80
|
-
assert_array_all(hash.values, value_class, type: type){"#{"#{yield}: " if block_given?}values"} unless value_class.nil?
|
|
77
|
+
assert_type(hash, Hash, type: AssertError) { 'hash' }
|
|
78
|
+
assert_array_all(hash.keys, key_class, type: type) { "#{"#{yield}: " if block_given?}keys" } unless key_class.nil?
|
|
79
|
+
assert_array_all(hash.values, value_class, type: type) { "#{"#{yield}: " if block_given?}values" } unless value_class.nil?
|
|
81
80
|
end
|
|
82
81
|
|
|
83
82
|
# Assert that value is one of the given values
|
|
@@ -86,7 +85,7 @@ module Aspera
|
|
|
86
85
|
# @param type [Exception,Symbol] Exception to raise, or Symbol for Log.log
|
|
87
86
|
# @yieldreturn [String] Additional description to prepend to the error message
|
|
88
87
|
def assert_values(value, values, type: AssertError)
|
|
89
|
-
assert_type(values, Array, type: AssertError){'values'}
|
|
88
|
+
assert_type(values, Array, type: AssertError) { 'values' }
|
|
90
89
|
assert(values.include?(value), type: type) do
|
|
91
90
|
val_list = values.inspect
|
|
92
91
|
val_list = "one of #{val_list}" if values.is_a?(Array)
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'aspera/agent/factory'
|
|
4
|
+
require 'aspera/schema/registry'
|
|
5
|
+
require 'aspera/schema/reader'
|
|
6
|
+
|
|
7
|
+
module Aspera
|
|
8
|
+
module Cli
|
|
9
|
+
# Mixin for Config plugin: ASCP / Transferd related actions
|
|
10
|
+
module AscpActions
|
|
11
|
+
TRANSFERD_APP_NAME = 'sdk'
|
|
12
|
+
|
|
13
|
+
# Mapping from agent symbol to schema key in options.schema.yaml
|
|
14
|
+
# Agents without dedicated schema (connect, desktop) are omitted.
|
|
15
|
+
AGENT_SCHEMA_KEY = {
|
|
16
|
+
direct: Schema::Registry::DIRECT_AGENT_OPTIONS,
|
|
17
|
+
node: Schema::Registry::NODE_AGENT_OPTIONS,
|
|
18
|
+
httpgw: Schema::Registry::HTTPGW_AGENT_OPTIONS,
|
|
19
|
+
transferd: Schema::Registry::TRANSFERD_AGENT_OPTIONS
|
|
20
|
+
}.freeze
|
|
21
|
+
private_constant :AGENT_SCHEMA_KEY
|
|
22
|
+
|
|
23
|
+
# Set the SDK directory, checking default and former locations
|
|
24
|
+
def set_sdk_dir
|
|
25
|
+
sdk_dir = Products::Transferd.sdk_directory rescue nil
|
|
26
|
+
if sdk_dir.nil?
|
|
27
|
+
@sdk_default_location = true
|
|
28
|
+
Log.log.debug('SDK folder is not set, checking default')
|
|
29
|
+
sdk_dir = self.class.default_app_main_folder(app_name: TRANSFERD_APP_NAME)
|
|
30
|
+
Log.log.debug { "Checking: #{sdk_dir}" }
|
|
31
|
+
if !Dir.exist?(sdk_dir)
|
|
32
|
+
Log.log.debug { "No such folder: #{sdk_dir}" }
|
|
33
|
+
former_sdk_folder = File.join(self.class.default_app_main_folder(app_name: Info::CMD_NAME), TRANSFERD_APP_NAME)
|
|
34
|
+
Log.log.debug { "Checking: #{former_sdk_folder}" }
|
|
35
|
+
sdk_dir = former_sdk_folder if Dir.exist?(former_sdk_folder)
|
|
36
|
+
end
|
|
37
|
+
Log.log.debug { "Using: #{sdk_dir}" }
|
|
38
|
+
Products::Transferd.sdk_directory = sdk_dir
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Install the transfer SDK (ascp + transferd) from a URL or using the default source.
|
|
43
|
+
# Version defaults to +Info::SDK_VERSION+; pass +LATEST+ as argument to install the latest available version.
|
|
44
|
+
# @param version [String, nil] version to install; nil means use the default SDK version
|
|
45
|
+
# @return [Result::Status] installation result message
|
|
46
|
+
def install_transfer_sdk(version: nil)
|
|
47
|
+
asked_version = version.nil? ? Info::SDK_VERSION : version
|
|
48
|
+
asked_version = nil if asked_version.eql?(SpecialValues::LATEST)
|
|
49
|
+
sdk_url = options.get_option(:sdk_url, mandatory: true)
|
|
50
|
+
sdk_url = nil if sdk_url.eql?(SpecialValues::DEF)
|
|
51
|
+
name, ver, folder = Ascp::Installation.instance.install_sdk(url: sdk_url, version: asked_version)
|
|
52
|
+
return Result::Status.new("Installed #{name} version #{ver} in #{folder}")
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def action_ascp_show(**)
|
|
56
|
+
Result::Text.new(Ascp::Installation.instance.path(:ascp))
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def action_ascp_info(**)
|
|
60
|
+
data = Ascp::Installation.instance.ascp_info
|
|
61
|
+
data['ts'] = transfer.user_transfer_spec
|
|
62
|
+
DataRepository::ELEMENTS.each_with_object(data) { |i, h| h[i.to_s] = DataRepository.instance.item(i) }
|
|
63
|
+
SecretHider::ADDITIONAL_KEYS_TO_HIDE.concat(DataRepository::ELEMENTS.map(&:to_s))
|
|
64
|
+
Result::SingleObject.new(data)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def action_ascp_install(version: nil, **)
|
|
68
|
+
install_transfer_sdk(version: version)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def action_ascp_spec(**)
|
|
72
|
+
builder = Schema::Documentation.new(TerminalFormatter, Transfer::Spec::SCHEMA, include_option: true, agent_columns: true).build
|
|
73
|
+
Result::ObjectList.new(builder.rows, fields: builder.columns)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def action_ascp_schema(agent_name: nil, **)
|
|
77
|
+
schema = Transfer::Spec::SCHEMA.current.merge({'$comment'=>'DO NOT EDIT, this file was generated from the YAML.'})
|
|
78
|
+
schema['properties'] = schema['properties'].select { |_k, v| CommandLineBuilder.supported_by_agent(agent_name, v) } unless agent_name.nil?
|
|
79
|
+
schema['properties'] = schema['properties'].sort.to_h
|
|
80
|
+
Result::SingleObject.new(schema)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def action_ascp_errors(**)
|
|
84
|
+
error_data = []
|
|
85
|
+
Ascp::Management::ERRORS.each_pair do |code, prop|
|
|
86
|
+
error_data.push(code: code, mnemonic: prop[:c], retry: prop[:r], info: prop[:a])
|
|
87
|
+
end
|
|
88
|
+
Result::ObjectList.new(error_data)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def action_ascp_products_list(**)
|
|
92
|
+
Result::ObjectList.new(Ascp::Installation.instance.installed_products, fields: %w[name app_root])
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def action_agents_list(**)
|
|
96
|
+
rows = Agent::Factory::ALL.map do |sym, names|
|
|
97
|
+
schema_key = AGENT_SCHEMA_KEY[sym]
|
|
98
|
+
param_names =
|
|
99
|
+
if schema_key
|
|
100
|
+
Schema::Registry.instance.reader(schema_key).current['properties']&.keys&.sort&.join(', ') || ''
|
|
101
|
+
else
|
|
102
|
+
''
|
|
103
|
+
end
|
|
104
|
+
{
|
|
105
|
+
'name' => sym.to_s,
|
|
106
|
+
'short' => names[:short].to_s,
|
|
107
|
+
'parameters' => param_names
|
|
108
|
+
}
|
|
109
|
+
end.sort_by { |r| r['name'] }
|
|
110
|
+
Result::ObjectList.new(rows, fields: %w[name short parameters])
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def action_agents_show(agent_name:, **)
|
|
114
|
+
names = Agent::Factory::ALL[agent_name]
|
|
115
|
+
schema_key = AGENT_SCHEMA_KEY[agent_name]
|
|
116
|
+
agent_info = {
|
|
117
|
+
'name' => agent_name.to_s,
|
|
118
|
+
'short' => names[:short].to_s,
|
|
119
|
+
'description' => schema_key ? Schema::Registry.instance.reader(schema_key).current['description'].to_s : '(no configurable parameters)'
|
|
120
|
+
}
|
|
121
|
+
if schema_key
|
|
122
|
+
properties = Schema::Registry.instance.reader(schema_key).current['properties'] || {}
|
|
123
|
+
rows = properties.map do |pname, pdef|
|
|
124
|
+
row = {'parameter' => pname, 'type' => pdef['type'].to_s, 'description' => pdef['description'].to_s}
|
|
125
|
+
row['required'] = (Schema::Registry.instance.reader(schema_key).current['required'] || []).include?(pname) ? 'yes' : 'no'
|
|
126
|
+
row['default'] = pdef.key?('default') ? pdef['default'].inspect : ''
|
|
127
|
+
row['enum'] = pdef.key?('enum') ? pdef['enum'].join(', ') : ''
|
|
128
|
+
row
|
|
129
|
+
end
|
|
130
|
+
return Result::ObjectList.new(rows, fields: %w[parameter required type default enum description])
|
|
131
|
+
end
|
|
132
|
+
Result::SingleObject.new(agent_info)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def action_agents_parameters(agent_name:, **)
|
|
136
|
+
schema_key = AGENT_SCHEMA_KEY[agent_name]
|
|
137
|
+
return Result::Nothing.new if schema_key.nil?
|
|
138
|
+
properties = Schema::Registry.instance.reader(schema_key).current['properties'] || {}
|
|
139
|
+
rows = properties.map do |pname, pdef|
|
|
140
|
+
{'name' => pname, 'type' => pdef['type'].to_s, 'description' => pdef['description'].to_s}
|
|
141
|
+
end
|
|
142
|
+
Result::ObjectList.new(rows, fields: %w[name type description])
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def action_transferd_install(**)
|
|
146
|
+
install_transfer_sdk
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def action_transferd_list(**)
|
|
150
|
+
sdk_list = Ascp::Installation.instance.sdk_locations
|
|
151
|
+
Result::ObjectList.new(sdk_list, fields: sdk_list.first.keys - ['url'])
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|