aspera-cli 4.27.2 → 4.27.4
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 +133 -0
- data/CONTRIBUTING.md +5 -2
- data/bin/ascli +3 -1
- data/docs/README.md +1006 -766
- data/lib/aspera/agent/base.rb +7 -2
- data/lib/aspera/agent/connect.rb +6 -8
- data/lib/aspera/agent/desktop.rb +2 -6
- data/lib/aspera/agent/direct.rb +52 -22
- data/lib/aspera/agent/node.rb +9 -8
- data/lib/aspera/agent/transferd.rb +2 -2
- data/lib/aspera/api/alee.rb +1 -1
- data/lib/aspera/api/aoc.rb +14 -12
- data/lib/aspera/api/ats.rb +1 -1
- data/lib/aspera/api/cos_node.rb +2 -2
- data/lib/aspera/api/faspex.rb +11 -7
- data/lib/aspera/api/httpgw.rb +38 -35
- data/lib/aspera/api/node.rb +39 -34
- data/lib/aspera/ascmd.rb +3 -1
- data/lib/aspera/ascp/installation.rb +63 -28
- data/lib/aspera/ascp/management.rb +1 -0
- data/lib/aspera/assert.rb +4 -0
- data/lib/aspera/cli/ascp_actions.rb +20 -41
- data/lib/aspera/cli/async_transfer_store.rb +12 -11
- data/lib/aspera/cli/bootstrapper.rb +14 -16
- data/lib/aspera/cli/command_line.rb +252 -0
- data/lib/aspera/cli/command_registry.rb +215 -37
- data/lib/aspera/cli/command_spec.rb +104 -15
- data/lib/aspera/cli/completion/ascli.bash +12 -0
- data/lib/aspera/cli/completion/ascli.fish +16 -0
- data/lib/aspera/cli/completion/ascli.zsh +19 -0
- data/lib/aspera/cli/context.rb +3 -0
- data/lib/aspera/cli/deprecation.rb +37 -0
- data/lib/aspera/cli/extended_value.rb +6 -3
- data/lib/aspera/cli/formatter.rb +94 -80
- data/lib/aspera/cli/gem_checker.rb +1 -1
- data/lib/aspera/cli/hints.rb +7 -6
- data/lib/aspera/cli/http.rb +22 -34
- data/lib/aspera/cli/info.rb +3 -0
- data/lib/aspera/cli/mcp_tool.rb +47 -83
- data/lib/aspera/cli/option_declarator.rb +33 -42
- data/lib/aspera/cli/option_registry.rb +69 -0
- data/lib/aspera/cli/option_types.rb +105 -0
- data/lib/aspera/cli/option_value.rb +278 -0
- data/lib/aspera/cli/options.schema.yaml +124 -15
- data/lib/aspera/cli/parser.rb +333 -862
- data/lib/aspera/cli/plugins/alee.rb +7 -4
- data/lib/aspera/cli/plugins/aoc.rb +545 -518
- data/lib/aspera/cli/plugins/ats.rb +59 -80
- data/lib/aspera/cli/plugins/base.rb +221 -265
- data/lib/aspera/cli/plugins/basic_auth.rb +2 -10
- data/lib/aspera/cli/plugins/config.rb +263 -184
- data/lib/aspera/cli/plugins/console.rb +103 -39
- data/lib/aspera/cli/plugins/cos.rb +6 -23
- data/lib/aspera/cli/plugins/factory.rb +3 -0
- data/lib/aspera/cli/plugins/faspex5.rb +204 -182
- data/lib/aspera/cli/plugins/faspio.rb +6 -11
- data/lib/aspera/cli/plugins/httpgw.rb +8 -11
- data/lib/aspera/cli/plugins/mcp.rb +20 -55
- data/lib/aspera/cli/plugins/node.rb +300 -327
- data/lib/aspera/cli/plugins/orchestrator.rb +152 -110
- data/lib/aspera/cli/plugins/preview.rb +96 -105
- data/lib/aspera/cli/plugins/server.rb +78 -53
- data/lib/aspera/cli/plugins/shares.rb +80 -131
- data/lib/aspera/cli/preset_actions.rb +44 -27
- data/lib/aspera/cli/preset_manager.rb +44 -19
- data/lib/aspera/cli/prompt.rb +36 -0
- data/lib/aspera/cli/result.rb +42 -36
- data/lib/aspera/cli/runner.rb +32 -59
- data/lib/aspera/cli/special_values.rb +5 -0
- data/lib/aspera/cli/sync_actions.rb +51 -46
- data/lib/aspera/cli/terminal_formatter.rb +9 -3
- data/lib/aspera/cli/transfer_actions.rb +14 -9
- data/lib/aspera/cli/transfer_agent.rb +34 -38
- data/lib/aspera/cli/transfer_progress.rb +290 -55
- data/lib/aspera/cli/vault_manager.rb +0 -17
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/cli/wizard.rb +5 -3
- data/lib/aspera/coverage.rb +1 -1
- data/lib/aspera/environment.rb +35 -5
- data/lib/aspera/faspex_gw.rb +2 -1
- data/lib/aspera/faspex_postproc.rb +1 -0
- data/lib/aspera/graphql.rb +5 -5
- data/lib/aspera/json_rpc/client.rb +5 -5
- data/lib/aspera/keychain/encrypted_hash.rb +2 -2
- data/lib/aspera/keychain/factory.rb +2 -1
- data/lib/aspera/keychain/one_password_api.rb +1 -1
- data/lib/aspera/link_header.rb +2 -2
- data/lib/aspera/log.rb +47 -27
- data/lib/aspera/markdown.rb +2 -0
- data/lib/aspera/mime.rb +25 -0
- data/lib/aspera/node_emulator.rb +759 -0
- data/lib/aspera/oauth/base.rb +37 -26
- data/lib/aspera/oauth/factory.rb +7 -3
- data/lib/aspera/oauth/generic.rb +1 -1
- data/lib/aspera/oauth/json_credentials.rb +34 -0
- data/lib/aspera/oauth/jwt.rb +4 -5
- data/lib/aspera/oauth/web.rb +9 -8
- data/lib/aspera/oauth.rb +1 -0
- data/lib/aspera/persistency_folder.rb +1 -3
- data/lib/aspera/preview/file_types.rb +4 -4
- data/lib/aspera/preview/generator.rb +11 -1
- data/lib/aspera/preview/options.schema.yaml +119 -0
- data/lib/aspera/preview/terminal.rb +4 -3
- data/lib/aspera/preview/utils.rb +9 -6
- data/lib/aspera/products/connect.rb +1 -1
- data/lib/aspera/rainbow.rb +7 -0
- data/lib/aspera/rest/aspera_errors.rb +72 -0
- data/lib/aspera/rest/call_error.rb +27 -0
- data/lib/aspera/rest/client.rb +523 -0
- data/lib/aspera/rest/error_analyzer.rb +113 -0
- data/lib/aspera/rest/list.rb +149 -0
- data/lib/aspera/rest/parameters.rb +55 -0
- data/lib/aspera/rest/util.rb +176 -0
- data/lib/aspera/rest.rb +7 -621
- data/lib/aspera/schema/IBM Aspera Console-enhanced.yaml +1125 -0
- data/lib/aspera/schema/IBM Aspera Faspex API-5.0-enhanced.yaml +0 -20
- data/lib/aspera/schema/IBM Aspera Orchestrator API-v1.yaml +1784 -0
- data/lib/aspera/schema/IBM Aspera on Cloud API-0.2.6-enhanced.yaml +1230 -137
- data/lib/aspera/schema/IBM Aspera on Cloud Automation API-1.0.5-enhanced.yaml +2395 -0
- data/lib/aspera/schema/IBM_Aspera_Shares.yaml +14 -13
- data/lib/aspera/schema/documentation.rb +13 -3
- data/lib/aspera/schema/reader.rb +12 -18
- data/lib/aspera/schema/registry.rb +23 -1
- data/lib/aspera/schema/validator.rb +92 -0
- data/lib/aspera/secret_hider.rb +36 -25
- data/lib/aspera/string_ext.rb +15 -0
- data/lib/aspera/temp_file_manager.rb +6 -5
- data/lib/aspera/transfer/parameters.rb +2 -0
- data/lib/aspera/transfer/spec.rb +1 -0
- data/lib/aspera/uri_reader.rb +11 -11
- data/lib/aspera/web_auth/index.html +147 -0
- data/lib/aspera/web_auth/server.rb +81 -0
- data.tar.gz.sig +0 -0
- metadata +43 -9
- metadata.gz.sig +0 -0
- data/lib/aspera/colors.rb +0 -79
- data/lib/aspera/node_simulator.rb +0 -344
- data/lib/aspera/preview/options.rb +0 -45
- data/lib/aspera/rest_call_error.rb +0 -25
- data/lib/aspera/rest_error_analyzer.rb +0 -111
- data/lib/aspera/rest_errors_aspera.rb +0 -58
- data/lib/aspera/rest_list.rb +0 -136
- data/lib/aspera/web_auth.rb +0 -211
|
@@ -8,6 +8,7 @@ require 'aspera/cli/extended_value'
|
|
|
8
8
|
require 'aspera/cli/special_values'
|
|
9
9
|
require 'aspera/cli/version'
|
|
10
10
|
require 'aspera/cli/formatter'
|
|
11
|
+
require 'aspera/cli/http'
|
|
11
12
|
require 'aspera/cli/info'
|
|
12
13
|
require 'aspera/cli/wizard'
|
|
13
14
|
require 'aspera/cli/sync_actions'
|
|
@@ -30,6 +31,7 @@ require 'aspera/rest'
|
|
|
30
31
|
require 'aspera/oauth/jwt'
|
|
31
32
|
require 'aspera/log'
|
|
32
33
|
require 'aspera/assert'
|
|
34
|
+
require 'aspera/string_ext'
|
|
33
35
|
require 'openssl'
|
|
34
36
|
require 'digest'
|
|
35
37
|
require 'open3'
|
|
@@ -81,66 +83,66 @@ module Aspera
|
|
|
81
83
|
end
|
|
82
84
|
|
|
83
85
|
DEFAULT_CHECK_NEW_VERSION_DAYS = 7
|
|
84
|
-
|
|
86
|
+
# Shells with a completion script in COMPLETION_FOLDER
|
|
87
|
+
COMPLETION_SHELLS = %i[bash zsh fish].freeze
|
|
88
|
+
COMPLETION_FOLDER = File.expand_path('../completion', __dir__)
|
|
89
|
+
private_constant :DEFAULT_CHECK_NEW_VERSION_DAYS, :COMPLETION_SHELLS, :COMPLETION_FOLDER
|
|
85
90
|
|
|
86
|
-
option :preset, description: 'Load the named option preset from current config file', short: 'P',
|
|
91
|
+
option :preset, description: 'Load the named option preset from current config file', short: 'P', on_set: :load_preset
|
|
87
92
|
option :version_check_days, description: 'Period in days to check new version (zero to disable)', allowed: Type::INTEGER, default: DEFAULT_CHECK_NEW_VERSION_DAYS
|
|
88
|
-
option :plugin_folder, description: 'Folder where to find additional plugins',
|
|
93
|
+
option :plugin_folder, description: 'Folder where to find additional plugins', on_set: :add_plugin_folders
|
|
89
94
|
option :sdk_url, description: 'Ascp: URL to get Aspera Transfer Executables', default: SpecialValues::DEF
|
|
90
|
-
option :locations_url, description: 'Ascp: URL to get download locations of Aspera Transfer Daemon',
|
|
91
|
-
option :sdk_folder, description: 'Ascp: Path to folder with ascp (or product with "product:")',
|
|
95
|
+
option :locations_url, description: 'Ascp: URL to get download locations of Aspera Transfer Daemon', default: Ascp::Installation.instance.transferd_urls, on_set: Ascp::Installation.instance.method(:transferd_urls=)
|
|
96
|
+
option :sdk_folder, description: 'Ascp: Path to folder with ascp (or product with "product:")', on_set: Ascp::Installation.instance.method(:sdk_folder=)
|
|
92
97
|
option :smtp, schema: Schema::Registry::SMTP_OPTIONS
|
|
93
98
|
option :notify_to, description: 'Email: Recipient for notification of transfers'
|
|
94
99
|
option :notify_template, description: 'Email: ERB template for notification of transfers'
|
|
95
|
-
option :cache_tokens, description: 'Save and reuse OAuth tokens', allowed: Type::BOOLEAN, default: true
|
|
100
|
+
option :cache_tokens, description: 'Save and reuse OAuth tokens', allowed: Type::BOOLEAN, default: true
|
|
101
|
+
option :expand_mounts, description: 'Commands: list commands of sub-trees provided by another plugin', allowed: Type::BOOLEAN, default: false
|
|
102
|
+
option :no_default, description: 'Do not load default configuration for plugin', allowed: Type::NONE, short: 'N', on_set: -> { presets.use_plugin_defaults = false }
|
|
103
|
+
use_options Wizard
|
|
104
|
+
# `on_set` callbacks of HTTP options are methods of `http_config`
|
|
105
|
+
use_options Http, target: -> { http_config }
|
|
96
106
|
|
|
97
107
|
def initialize(**_)
|
|
98
108
|
super
|
|
99
109
|
@vault_instance = nil
|
|
100
110
|
@sdk_default_location = false
|
|
101
|
-
@option_cache_tokens = true
|
|
102
|
-
# :no_default uses a &block callback - must stay imperative
|
|
103
|
-
options.declare(:no_default, description: 'Do not load default configuration for plugin', allowed: Type::NONE, short: 'N') { presets.use_plugin_defaults = false }
|
|
104
|
-
# Declare wizard options (Wizard#initialize calls options.declare internally)
|
|
105
111
|
@wizard = Wizard.new(self, context.main_folder)
|
|
112
|
+
# Values set through `on_set` callbacks are used below (sdk_folder)
|
|
106
113
|
options.parse_options!
|
|
107
114
|
set_sdk_dir
|
|
108
|
-
# HTTP options: declare metadata (class method), then bind to the instance
|
|
109
|
-
Http.declare_options(options)
|
|
110
|
-
context.http_config.bind_options(options)
|
|
111
|
-
options.parse_options!
|
|
112
115
|
end
|
|
113
116
|
|
|
114
|
-
# DSL command declarations - replaces ACTIONS + execute_action
|
|
115
117
|
command :preset, description: 'Manage configuration presets'
|
|
116
118
|
commands_under :preset do
|
|
117
|
-
command :list, description: 'List all presets'
|
|
118
|
-
command :overview, description: '
|
|
119
|
+
command :list, description: 'List all presets', action: ->(**) { Result::ValueList.new(presets.config_presets.keys, name: 'name') }
|
|
120
|
+
command :overview, description: 'Show all options from all presets'
|
|
119
121
|
command :lookup, description: 'Find preset matching URL and username'
|
|
120
122
|
command :secure, description: 'Move secrets to vault',
|
|
121
|
-
arguments: [{name: :config_name,
|
|
122
|
-
command :show, description: '
|
|
123
|
+
arguments: [{name: :config_name, mandatory: false}]
|
|
124
|
+
command :show, description: 'Show a preset',
|
|
123
125
|
arguments: [{name: :name, type: :identifier}]
|
|
124
126
|
command :delete, description: 'Delete a preset',
|
|
125
127
|
arguments: [{name: :name, type: :identifier}]
|
|
126
|
-
command :get, description: '
|
|
127
|
-
arguments: [{name: :name, type: :identifier}, {name: :param_name
|
|
128
|
+
command :get, description: 'Show a single parameter of a preset',
|
|
129
|
+
arguments: [{name: :name, type: :identifier}, {name: :param_name}]
|
|
128
130
|
command :unset, description: 'Remove a parameter from a preset',
|
|
129
|
-
arguments: [{name: :name, type: :identifier}, {name: :param_name
|
|
131
|
+
arguments: [{name: :name, type: :identifier}, {name: :param_name}]
|
|
130
132
|
command :set, description: 'Set a parameter in a preset',
|
|
131
|
-
arguments: [{name: :name, type: :identifier}, {name: :param_name
|
|
133
|
+
arguments: [{name: :name, type: :identifier}, {name: :param_name},
|
|
132
134
|
{name: :param_value, type: nil}]
|
|
133
135
|
command :initialize, description: 'Initialize a preset with a value',
|
|
134
|
-
arguments: [{name: :name, type: :identifier}, {name: :
|
|
136
|
+
arguments: [{name: :name, type: :identifier}, {name: :preset, type: Hash}]
|
|
135
137
|
command :update, description: 'Update a preset with current option values',
|
|
136
138
|
arguments: [{name: :name, type: :identifier}]
|
|
137
|
-
command :ask, description: '
|
|
139
|
+
command :ask, description: 'Ask for option values interactively',
|
|
138
140
|
arguments: [{name: :name, type: :identifier},
|
|
139
|
-
{name: :option_names,
|
|
141
|
+
{name: :option_names, multiple: true, interactive: true}]
|
|
140
142
|
end
|
|
141
143
|
command(
|
|
142
144
|
:open, description: 'Open the configuration file in the default editor',
|
|
143
|
-
action: lambda do
|
|
145
|
+
action: lambda do |**|
|
|
144
146
|
Environment.instance.open_editor(context.presets.config_file.to_s)
|
|
145
147
|
Result::Nothing.new
|
|
146
148
|
end
|
|
@@ -148,106 +150,163 @@ module Aspera
|
|
|
148
150
|
command :documentation, description: 'Open the documentation in the default browser',
|
|
149
151
|
arguments: [
|
|
150
152
|
{name: :location, type: Symbol, mandatory: false, default: :github, allowed: %i[github local toc]},
|
|
151
|
-
{name: :section,
|
|
153
|
+
{name: :section, mandatory: false}
|
|
152
154
|
]
|
|
153
|
-
command
|
|
155
|
+
command(
|
|
156
|
+
:genkey, description: 'Generate a new RSA private key',
|
|
154
157
|
arguments: [
|
|
155
|
-
{name: :private_key_path
|
|
158
|
+
{name: :private_key_path},
|
|
156
159
|
{name: :private_key_length, type: Integer, mandatory: false, default: OAuth::Jwt::DEFAULT_PRIV_KEY_LENGTH}
|
|
157
|
-
]
|
|
158
|
-
|
|
159
|
-
|
|
160
|
+
],
|
|
161
|
+
action: lambda do |private_key_path:, private_key_length: OAuth::Jwt::DEFAULT_PRIV_KEY_LENGTH, **|
|
|
162
|
+
OAuth::Jwt.generate_rsa_private_key(path: private_key_path, length: private_key_length)
|
|
163
|
+
Result::Status.new("Generated #{private_key_length} bit RSA key: #{private_key_path}")
|
|
164
|
+
end
|
|
165
|
+
)
|
|
166
|
+
command :pubkey, description: 'Show the public key of an RSA private key',
|
|
167
|
+
arguments: [{name: :private_key_pem}],
|
|
160
168
|
action: ->(private_key_pem:, **) { Result::Text.new(OpenSSL::PKey::RSA.new(private_key_pem).public_key.to_s) }
|
|
161
169
|
command :remote_certificate, description: 'Retrieve the certificate chain of a remote HTTPS server'
|
|
162
|
-
command :echo, description: '
|
|
170
|
+
command :echo, description: 'Show the value of a given argument',
|
|
163
171
|
arguments: [{name: :value, type: nil}],
|
|
164
172
|
action: ->(value:, **) { Result.auto(value) }
|
|
165
173
|
command :download, description: 'Download a file from a URL',
|
|
166
174
|
arguments: [
|
|
167
|
-
{name: :file_url
|
|
168
|
-
{name: :file_dest,
|
|
175
|
+
{name: :file_url},
|
|
176
|
+
{name: :file_dest, mandatory: false}
|
|
169
177
|
]
|
|
170
178
|
command :tokens, description: 'Manage OAuth tokens'
|
|
171
179
|
command :plugins, description: 'Manage CLI plugins'
|
|
172
180
|
command :detect, description: 'Detect the Aspera product from a URL (interactive)',
|
|
173
|
-
arguments: [{name: :url,
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
181
|
+
arguments: [{name: :url, interactive: true}, {name: :plugin_name, mandatory: false, default: nil}],
|
|
182
|
+
action: ->(url:, plugin_name: nil, **) { Result::ObjectList.new(@wizard.identify_plugins_for_url(url: url, plugin_name: plugin_name).freeze) }
|
|
183
|
+
command(
|
|
184
|
+
:wizard, description: 'Run the setup wizard for an Aspera product (interactive)',
|
|
185
|
+
arguments: [{name: :url, interactive: true}, {name: :plugin_name, mandatory: false, default: nil},
|
|
186
|
+
{name: :preset_name, mandatory: false, default: ''}],
|
|
187
|
+
action: lambda do |url:, plugin_name: nil, preset_name: '', **|
|
|
188
|
+
apps = @wizard.identify_plugins_for_url(url: url, plugin_name: plugin_name).freeze
|
|
189
|
+
# The product wizard prompts for missing mandatory options (username, password, ...)
|
|
190
|
+
options.with_interactive { @wizard.find(apps, preset_name: preset_name) }
|
|
191
|
+
end
|
|
192
|
+
)
|
|
193
|
+
command :coffee, description: 'Show a coffee image', action: ->(**) { Result::Image.new(COFFEE_IMAGE_URL) }
|
|
194
|
+
command :image, description: 'Show an image',
|
|
179
195
|
arguments: [{name: :image_uri, type: nil}],
|
|
180
196
|
action: ->(image_uri:, **) { Result::Image.new(image_uri) }
|
|
181
197
|
command :sync, description: 'Manage Aspera Sync operations'
|
|
182
|
-
command :gem, description: '
|
|
183
|
-
command :folder, description: '
|
|
184
|
-
command :file, description: '
|
|
198
|
+
command :gem, description: 'Show gem information'
|
|
199
|
+
command :folder, description: 'Show the configuration folder path', action: ->(**) { Result::Text.new(context.main_folder) }
|
|
200
|
+
command :file, description: 'Show the configuration file path', action: ->(**) { Result::Text.new(context.presets.config_file) }
|
|
185
201
|
command(
|
|
186
202
|
:email_test, description: 'Send a test email',
|
|
187
|
-
action: lambda do
|
|
203
|
+
action: lambda do |**|
|
|
188
204
|
context.mailer.send_email_template(email_template_default: EMAIL_TEST_TEMPLATE)
|
|
189
205
|
Result::Nothing.new
|
|
190
206
|
end
|
|
191
207
|
)
|
|
192
|
-
command :smtp_settings, description: '
|
|
208
|
+
command :smtp_settings, description: 'Show the current SMTP settings', action: ->(**) { Result::SingleObject.new(context.mailer.email_settings) }
|
|
193
209
|
command(
|
|
194
210
|
:proxy_check, description: 'Check the proxy returned by the PAC script for a given URL',
|
|
195
|
-
arguments: [{name: :server_url
|
|
211
|
+
arguments: [{name: :server_url}],
|
|
196
212
|
action: lambda do |server_url:, **|
|
|
197
213
|
Aspera.assert(!context.pac_executor.nil?, type: Cli::BadArgument) { 'No PAC script configured, use --fpac' }
|
|
198
214
|
Result::ValueList.new(context.pac_executor.get_proxies(server_url), name: 'proxy')
|
|
199
215
|
end
|
|
200
216
|
)
|
|
201
|
-
command :check_update, description: 'Check if a newer version of the gem is available', action: -> { Result::SingleObject.new(check_gem_version) }
|
|
217
|
+
command :check_update, description: 'Check if a newer version of the gem is available', action: ->(**) { Result::SingleObject.new(check_gem_version) }
|
|
202
218
|
command :initdemo, description: 'Initialize the demo server preset'
|
|
203
219
|
command :vault, description: 'Manage secrets in the vault'
|
|
204
220
|
commands_under :vault do
|
|
205
|
-
command :info, description: '
|
|
221
|
+
command :info, description: 'Show vault information',
|
|
206
222
|
action: ->(**) { Result::SingleObject.new(vault_required.info) }
|
|
207
223
|
command :ids, description: 'List secret labels in the vault',
|
|
208
224
|
action: ->(**) { Result::ObjectList.new(vault_required.ids) }
|
|
209
225
|
command :list, description: 'List all secrets with full details',
|
|
210
226
|
action: ->(**) { Result::ObjectList.new(vault_required.all) }
|
|
211
|
-
command
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
227
|
+
command(
|
|
228
|
+
:show, description: 'Show a secret by label (or id)',
|
|
229
|
+
arguments: [{name: :label}, {name: :id, mandatory: false, default: nil}],
|
|
230
|
+
action: lambda do |label:, id: nil, **|
|
|
231
|
+
v = vault_required
|
|
232
|
+
kwargs = id && v.method(:get).parameters.any? { |_t, n| n == :id } ? {id: id} : {}
|
|
233
|
+
Result::SingleObject.new(v.get(label: label, **kwargs))
|
|
234
|
+
end
|
|
235
|
+
)
|
|
236
|
+
command(
|
|
237
|
+
:create, description: 'Add a new secret to the vault',
|
|
238
|
+
arguments: [{name: :secret, type: Hash, schema: Schema::Registry::VAULT_SECRET}],
|
|
239
|
+
action: lambda do |secret:, **|
|
|
240
|
+
vault_required.set(secret.symbolize_keys)
|
|
241
|
+
Result::Status.new('Secret added')
|
|
242
|
+
end
|
|
243
|
+
)
|
|
244
|
+
command :delete, description: 'Delete a secret by label (or id)',
|
|
245
|
+
arguments: [{name: :label}, {name: :id, mandatory: false, default: nil}]
|
|
246
|
+
command(
|
|
247
|
+
:password, description: 'Change the vault password',
|
|
248
|
+
arguments: [{name: :new_password}],
|
|
249
|
+
action: lambda do |new_password:, **|
|
|
250
|
+
Aspera.assert(vault_required.respond_to?(:change_password), 'Vault does not support password change')
|
|
251
|
+
vault_required.change_password(new_password)
|
|
252
|
+
Result::Status.new('Vault password updated')
|
|
253
|
+
end
|
|
254
|
+
)
|
|
255
|
+
command :import, description: 'Import secrets from a JSON array (supports --bulk)',
|
|
220
256
|
arguments: [{name: :secrets, type: Array, schema: {type: 'array', items: {'$ref' => Schema::Registry::VAULT_SECRET}}}]
|
|
221
257
|
end
|
|
222
|
-
command :commands, description: 'List all available commands
|
|
258
|
+
command :commands, description: 'List all available commands, of all plugins or only the given one, optionally under a command path',
|
|
259
|
+
arguments: [{name: :plugin_name, mandatory: false, default: nil},
|
|
260
|
+
{name: :command_path, multiple: true, mandatory: false, default: nil}]
|
|
223
261
|
command :options, description: 'List all options available for a plugin',
|
|
224
|
-
arguments: [{name: :plugin_name
|
|
262
|
+
arguments: [{name: :plugin_name}]
|
|
225
263
|
command :test, description: 'Internal test commands'
|
|
226
|
-
command :platform, description: '
|
|
227
|
-
command :completion, description: '
|
|
264
|
+
command :platform, description: 'Show the current platform/architecture', action: ->(**) { Result::Text.new(Environment.instance.architecture) }
|
|
265
|
+
command :completion, description: 'Shell completion: activation scripts and next words'
|
|
228
266
|
|
|
229
267
|
# remote_certificate sub-commands
|
|
230
268
|
commands_under :remote_certificate do
|
|
231
|
-
command
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
269
|
+
command(
|
|
270
|
+
:chain, description: 'Show the full certificate chain as PEM',
|
|
271
|
+
arguments: [{name: :remote_url}],
|
|
272
|
+
action: lambda do |remote_url:, **|
|
|
273
|
+
remote_chain = Rest.remote_certificate_chain(remote_url, as_string: false)
|
|
274
|
+
Aspera.assert(remote_chain&.first) { "No certificate found for #{remote_url}" }
|
|
275
|
+
Result::Text.new(remote_chain.map(&:to_pem).join("\n"))
|
|
276
|
+
end
|
|
277
|
+
)
|
|
278
|
+
command(
|
|
279
|
+
:only, description: 'Show only the server certificate as PEM',
|
|
280
|
+
arguments: [{name: :remote_url}],
|
|
281
|
+
action: lambda do |remote_url:, **|
|
|
282
|
+
remote_chain = Rest.remote_certificate_chain(remote_url, as_string: false)
|
|
283
|
+
Aspera.assert(remote_chain&.first) { "No certificate found for #{remote_url}" }
|
|
284
|
+
Result::Text.new(remote_chain.first.to_pem)
|
|
285
|
+
end
|
|
286
|
+
)
|
|
287
|
+
command(
|
|
288
|
+
:name, description: 'Show the CN of the server certificate',
|
|
289
|
+
arguments: [{name: :remote_url}],
|
|
290
|
+
action: lambda do |remote_url:, **|
|
|
291
|
+
remote_chain = Rest.remote_certificate_chain(remote_url, as_string: false)
|
|
292
|
+
Aspera.assert(remote_chain&.first) { "No certificate found for #{remote_url}" }
|
|
293
|
+
Result::Text.new(remote_chain.first.subject.to_a.find { |name, _, _| name == 'CN' }[1])
|
|
294
|
+
end
|
|
295
|
+
)
|
|
237
296
|
end
|
|
238
297
|
|
|
239
298
|
# tokens sub-commands
|
|
240
299
|
commands_under :tokens do
|
|
241
300
|
command(
|
|
242
301
|
:flush, description: 'Delete all cached OAuth tokens',
|
|
243
|
-
action: lambda do
|
|
302
|
+
action: lambda do |**|
|
|
244
303
|
require 'aspera/api/node'
|
|
245
304
|
Result::ValueList.new(OAuth::Factory.instance.flush_tokens, name: 'file')
|
|
246
305
|
end
|
|
247
306
|
)
|
|
248
307
|
command(
|
|
249
308
|
:list, description: 'List all cached OAuth tokens',
|
|
250
|
-
action: lambda do
|
|
309
|
+
action: lambda do |**|
|
|
251
310
|
require 'aspera/api/node'
|
|
252
311
|
Result::ObjectList.new(OAuth::Factory.instance.persisted_tokens)
|
|
253
312
|
end
|
|
@@ -261,25 +320,33 @@ module Aspera
|
|
|
261
320
|
command :list, description: 'List all available plugins'
|
|
262
321
|
command :create, description: 'Create a new plugin skeleton file',
|
|
263
322
|
arguments: [
|
|
264
|
-
{name: :name
|
|
265
|
-
{name: :folder,
|
|
323
|
+
{name: :name},
|
|
324
|
+
{name: :folder, mandatory: false}
|
|
266
325
|
]
|
|
267
326
|
end
|
|
268
327
|
|
|
269
328
|
# ascp sub-commands
|
|
270
329
|
command :ascp, description: 'Manage FASP/ascp transfer engine'
|
|
271
330
|
commands_under :ascp do
|
|
272
|
-
command :show, description: '
|
|
273
|
-
command :info, description: '
|
|
331
|
+
command :show, description: 'Show ascp binary path', action: ->(**) { Result::Text.new(Ascp::Installation.instance.path(:ascp)) }
|
|
332
|
+
command :info, description: 'Show ascp and transfer spec information'
|
|
274
333
|
command :install, description: 'Install the transfer SDK',
|
|
275
|
-
arguments: [{name: :version, mandatory: false, default: nil}]
|
|
276
|
-
|
|
277
|
-
command
|
|
278
|
-
|
|
279
|
-
|
|
334
|
+
arguments: [{name: :version, mandatory: false, default: nil}],
|
|
335
|
+
action: ->(version: nil, **) { install_transfer_sdk(version: version) }
|
|
336
|
+
command(
|
|
337
|
+
:spec, description: 'Show the transfer spec schema',
|
|
338
|
+
action: lambda do |**|
|
|
339
|
+
builder = Schema::Documentation.new(TerminalFormatter, Transfer::Spec::SCHEMA, include_option: true, agent_columns: true).build
|
|
340
|
+
Result::ObjectList.new(builder.rows, fields: builder.columns)
|
|
341
|
+
end
|
|
342
|
+
)
|
|
343
|
+
command :schema, description: 'Show the transfer spec JSON schema',
|
|
344
|
+
arguments: [{name: :agent_name, allowed: Agent::Factory::ALL.keys, mandatory: false, default: nil}]
|
|
345
|
+
command :errors, description: 'Show FASP error codes'
|
|
280
346
|
command :products, description: 'Manage installed Aspera products'
|
|
281
347
|
commands_under :products do
|
|
282
|
-
command :list, description: 'List installed Aspera products'
|
|
348
|
+
command :list, description: 'List installed Aspera products',
|
|
349
|
+
action: ->(**) { Result::ObjectList.new(Ascp::Installation.instance.installed_products, fields: %w[name app_root]) }
|
|
283
350
|
end
|
|
284
351
|
end
|
|
285
352
|
|
|
@@ -296,75 +363,93 @@ module Aspera
|
|
|
296
363
|
# transfer async management sub-commands
|
|
297
364
|
command :transfer, description: 'Manage asynchronous transfers'
|
|
298
365
|
commands_under :transfer do
|
|
299
|
-
command
|
|
366
|
+
command(
|
|
367
|
+
:list, description: 'List all async transfer jobs',
|
|
368
|
+
action: lambda do |**|
|
|
369
|
+
rows = async_transfer_store.list
|
|
370
|
+
Result::ObjectList.new(rows, fields: %w[job_id agent_type status started_at ended_at bytes_transferred transfer_id])
|
|
371
|
+
end
|
|
372
|
+
)
|
|
300
373
|
command :status, description: 'Show status of an async transfer job',
|
|
301
|
-
arguments: [{name: :job_id
|
|
374
|
+
arguments: [{name: :job_id}]
|
|
302
375
|
command :cleanup, description: 'Remove completed/failed/cancelled transfer entries'
|
|
303
376
|
end
|
|
304
377
|
|
|
305
378
|
# transferd sub-commands
|
|
306
379
|
command :transferd, description: 'Manage the transfer daemon (transferd)'
|
|
307
380
|
commands_under :transferd do
|
|
308
|
-
command :install, description: 'Install the transfer daemon'
|
|
309
|
-
command
|
|
381
|
+
command :install, description: 'Install the transfer daemon', action: ->(**) { install_transfer_sdk }
|
|
382
|
+
command(
|
|
383
|
+
:list, description: 'List available SDK locations',
|
|
384
|
+
action: lambda do |**|
|
|
385
|
+
sdk_list = Ascp::Installation.instance.sdk_locations
|
|
386
|
+
Result::ObjectList.new(sdk_list, fields: sdk_list.first.keys - ['url'])
|
|
387
|
+
end
|
|
388
|
+
)
|
|
310
389
|
end
|
|
311
390
|
|
|
312
391
|
# sync sub-commands
|
|
313
392
|
commands_under :sync do
|
|
314
393
|
command(
|
|
315
|
-
:spec, description: '
|
|
316
|
-
action: lambda do
|
|
394
|
+
:spec, description: 'Show the sync configuration schema',
|
|
395
|
+
action: lambda do |**|
|
|
317
396
|
builder = Schema::Documentation.new(TerminalFormatter, Sync::Operations::CONF_SCHEMA, include_option: true).build
|
|
318
397
|
Result::ObjectList.new(builder.rows, fields: builder.columns)
|
|
319
398
|
end
|
|
320
399
|
)
|
|
321
|
-
|
|
322
|
-
SyncActions.register_sync_admin_commands(self, :admin)
|
|
400
|
+
sync_admin_commands :admin
|
|
323
401
|
command :translate, description: 'Translate async-style arguments to sync config format',
|
|
324
|
-
arguments: [{name: :async_arguments,
|
|
402
|
+
arguments: [{name: :async_arguments, multiple: true}],
|
|
325
403
|
action: ->(async_arguments:, **) { Result::SingleObject.new(Sync::Operations.args_to_conf(async_arguments)) }
|
|
326
404
|
end
|
|
327
405
|
|
|
328
406
|
# gem sub-commands
|
|
329
407
|
commands_under :gem do
|
|
330
|
-
command :path, description: '
|
|
331
|
-
command :version, description: '
|
|
332
|
-
command :name, description: '
|
|
408
|
+
command :path, description: 'Show the gem source root path', action: ->(**) { Result::Text.new(self.class.gem_src_root) }
|
|
409
|
+
command :version, description: 'Show the gem version', action: ->(**) { Result::Text.new(Cli::VERSION) }
|
|
410
|
+
command :name, description: 'Show the gem name', action: ->(**) { Result::Text.new(Info::GEM_NAME) }
|
|
333
411
|
end
|
|
334
412
|
|
|
335
413
|
# test sub-commands
|
|
336
414
|
commands_under :test do
|
|
337
|
-
command
|
|
415
|
+
command(
|
|
416
|
+
:throw, description: 'Raise an exception (for testing)',
|
|
338
417
|
arguments: [
|
|
339
|
-
{name: :exception_class_name
|
|
340
|
-
{name: :exception_text
|
|
341
|
-
]
|
|
342
|
-
|
|
418
|
+
{name: :exception_class_name},
|
|
419
|
+
{name: :exception_text}
|
|
420
|
+
],
|
|
421
|
+
action: lambda do |exception_class_name:, exception_text:, **|
|
|
422
|
+
type = Object.const_get(exception_class_name)
|
|
423
|
+
Aspera.assert(type <= Exception) { "#{type} is not an exception: #{type.class}" }
|
|
424
|
+
raise type, exception_text
|
|
425
|
+
end
|
|
426
|
+
)
|
|
427
|
+
command :web, description: 'Test web browser interaction', action: ->(**) {}
|
|
343
428
|
end
|
|
344
429
|
|
|
345
430
|
# completion sub-commands
|
|
346
431
|
commands_under :completion do
|
|
347
|
-
command :
|
|
348
|
-
arguments: [{name: :words,
|
|
432
|
+
command :words, description: 'List words that can follow the given words (used by completion scripts)',
|
|
433
|
+
arguments: [{name: :words, multiple: true, mandatory: false}]
|
|
434
|
+
COMPLETION_SHELLS.each do |shell|
|
|
435
|
+
command shell, description: "Generate #{shell} completion script, to evaluate in shell",
|
|
436
|
+
action: ->(**) { completion_script(shell) }
|
|
437
|
+
end
|
|
349
438
|
end
|
|
350
439
|
|
|
351
|
-
attr_accessor :option_cache_tokens
|
|
352
|
-
|
|
353
440
|
attr_reader :gem_url
|
|
354
441
|
|
|
355
|
-
|
|
442
|
+
# `on_set` callback of option `plugin_folder`
|
|
443
|
+
# @param value [String, Array<String>] folder(s) where to find plugins
|
|
444
|
+
def add_plugin_folders(value)
|
|
356
445
|
value = [value] unless value.is_a?(Array)
|
|
357
446
|
Aspera.assert_array_all(value, String) { 'plugin folder(s)' }
|
|
358
447
|
value.each { |f| Plugins::Factory.instance.add_lookup_folder(f) }
|
|
359
448
|
end
|
|
360
449
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
def option_preset; 'write-only option'; end
|
|
366
|
-
|
|
367
|
-
def option_preset=(value)
|
|
450
|
+
# `on_set` callback of option `preset`
|
|
451
|
+
# @param value [String, Hash] preset name, or set of option values
|
|
452
|
+
def load_preset(value)
|
|
368
453
|
case value
|
|
369
454
|
when Hash
|
|
370
455
|
options.add_option_preset(value, 'set')
|
|
@@ -406,34 +491,11 @@ module Aspera
|
|
|
406
491
|
end
|
|
407
492
|
end
|
|
408
493
|
|
|
409
|
-
def action_genkey(private_key_path:, private_key_length: OAuth::Jwt::DEFAULT_PRIV_KEY_LENGTH, **)
|
|
410
|
-
OAuth::Jwt.generate_rsa_private_key(path: private_key_path, length: private_key_length)
|
|
411
|
-
Result::Status.new("Generated #{private_key_length} bit RSA key: #{private_key_path}")
|
|
412
|
-
end
|
|
413
|
-
|
|
414
|
-
def action_remote_certificate_chain(remote_url:, **)
|
|
415
|
-
remote_chain = Rest.remote_certificate_chain(remote_url, as_string: false)
|
|
416
|
-
Aspera.assert(remote_chain&.first) { "No certificate found for #{remote_url}" }
|
|
417
|
-
Result::Text.new(remote_chain.map(&:to_pem).join("\n"))
|
|
418
|
-
end
|
|
419
|
-
|
|
420
|
-
def action_remote_certificate_only(remote_url:, **)
|
|
421
|
-
remote_chain = Rest.remote_certificate_chain(remote_url, as_string: false)
|
|
422
|
-
Aspera.assert(remote_chain&.first) { "No certificate found for #{remote_url}" }
|
|
423
|
-
Result::Text.new(remote_chain.first.to_pem)
|
|
424
|
-
end
|
|
425
|
-
|
|
426
|
-
def action_remote_certificate_name(remote_url:, **)
|
|
427
|
-
remote_chain = Rest.remote_certificate_chain(remote_url, as_string: false)
|
|
428
|
-
Aspera.assert(remote_chain&.first) { "No certificate found for #{remote_url}" }
|
|
429
|
-
Result::Text.new(remote_chain.first.subject.to_a.find { |name, _, _| name == 'CN' }[1])
|
|
430
|
-
end
|
|
431
|
-
|
|
432
494
|
def action_download(file_url:, file_dest: nil, **)
|
|
433
495
|
file_url = file_url.chomp
|
|
434
|
-
file_dest = File.join(transfer.destination_folder(Transfer::Spec::DIRECTION_RECEIVE), file_url.
|
|
496
|
+
file_dest = File.join(transfer.destination_folder(Transfer::Spec::DIRECTION_RECEIVE), file_url.rpartition('/').last) if file_dest.nil?
|
|
435
497
|
Log.log.info("Downloading: #{file_url}")
|
|
436
|
-
Rest.new(base_url: file_url).call(operation: 'GET', save_to: file_dest)
|
|
498
|
+
Rest::Client.new(base_url: file_url).call(operation: 'GET', save_to: file_dest)
|
|
437
499
|
Result::Status.new("Saved to: #{file_dest}")
|
|
438
500
|
end
|
|
439
501
|
|
|
@@ -444,7 +506,7 @@ module Aspera
|
|
|
444
506
|
Result::SingleObject.new(data)
|
|
445
507
|
end
|
|
446
508
|
|
|
447
|
-
def action_plugins_list
|
|
509
|
+
def action_plugins_list(**)
|
|
448
510
|
result = Plugins::Factory.instance.plugin_list.map do |name|
|
|
449
511
|
plugin_class = Plugins::Factory.instance.plugin_class(name)
|
|
450
512
|
{
|
|
@@ -467,10 +529,7 @@ module Aspera
|
|
|
467
529
|
module Cli
|
|
468
530
|
module Plugins
|
|
469
531
|
class #{plugin_name.snake_to_capital} < Base
|
|
470
|
-
command :example, description: 'example command', action:
|
|
471
|
-
def action_example
|
|
472
|
-
Result::Status.new('You called plugin #{plugin_name}')
|
|
473
|
-
end
|
|
532
|
+
command :example, description: 'example command', action: ->(**) { Result::Status.new('You called plugin #{plugin_name}') }
|
|
474
533
|
end
|
|
475
534
|
end
|
|
476
535
|
end
|
|
@@ -480,19 +539,7 @@ module Aspera
|
|
|
480
539
|
Result::Status.new("Created #{plugin_file}")
|
|
481
540
|
end
|
|
482
541
|
|
|
483
|
-
def
|
|
484
|
-
options.ask_missing_mandatory = true
|
|
485
|
-
apps = @wizard.identify_plugins_for_url(url: url, plugin_name: plugin_name).freeze
|
|
486
|
-
Result::ObjectList.new(apps)
|
|
487
|
-
end
|
|
488
|
-
|
|
489
|
-
def action_wizard(url:, plugin_name: nil, preset_name: '', **)
|
|
490
|
-
options.ask_missing_mandatory = true
|
|
491
|
-
apps = @wizard.identify_plugins_for_url(url: url, plugin_name: plugin_name).freeze
|
|
492
|
-
@wizard.find(apps, preset_name: preset_name)
|
|
493
|
-
end
|
|
494
|
-
|
|
495
|
-
def action_initdemo
|
|
542
|
+
def action_initdemo(**)
|
|
496
543
|
cp = presets.config_presets
|
|
497
544
|
if cp.key?(DEMO_PRESET)
|
|
498
545
|
Log.log.warn { "Demo server preset already present: #{DEMO_PRESET}" }
|
|
@@ -516,55 +563,79 @@ module Aspera
|
|
|
516
563
|
Result::Status.new('Done')
|
|
517
564
|
end
|
|
518
565
|
|
|
519
|
-
def action_commands
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
566
|
+
def action_commands(plugin_name: nil, command_path: nil, **)
|
|
567
|
+
plugin_names = Plugins::Factory.instance.plugin_list
|
|
568
|
+
unless plugin_name.nil?
|
|
569
|
+
Aspera.assert_values(plugin_name.to_sym, plugin_names, type: Cli::BadArgument)
|
|
570
|
+
plugin_names = [plugin_name.to_sym]
|
|
571
|
+
end
|
|
572
|
+
words = Array(command_path).map(&:to_sym)
|
|
573
|
+
Aspera.assert(words.empty? || plugin_names.length.eql?(1), type: Cli::BadArgument) { 'command path requires a plugin name' }
|
|
574
|
+
expand_mounts = options.get_option(:expand_mounts)
|
|
575
|
+
commands = plugin_names.flat_map do |name|
|
|
576
|
+
reg = Plugins::Factory.instance.plugin_class(name).command_registry
|
|
577
|
+
prefix = listed_command_path(name, reg, words)
|
|
578
|
+
paths = prefix.empty? || reg.children_of(prefix).any? ? reg.leaf_paths(prefix, expand_mounts: expand_mounts) : [prefix]
|
|
579
|
+
paths.map do |path|
|
|
580
|
+
mount = expand_mounts ? nil : reg.mount_at(path)
|
|
581
|
+
description = reg[path]&.description.to_s
|
|
582
|
+
if mount
|
|
583
|
+
target = mount.plugin.name.split('::').last.downcase
|
|
584
|
+
description = "#{description} (see: #{command_syntax(target, mount.registry, mount.at)})"
|
|
535
585
|
end
|
|
536
|
-
syntax = tokens.join(' ')
|
|
537
586
|
{
|
|
538
|
-
syntax:
|
|
539
|
-
description:
|
|
587
|
+
syntax: command_syntax(name, reg, path) + (mount ? ' <command...>' : ''),
|
|
588
|
+
description: description
|
|
540
589
|
}
|
|
541
590
|
end
|
|
542
591
|
end
|
|
543
592
|
Result::ObjectList.new(commands, fields: %w[syntax description])
|
|
544
593
|
end
|
|
545
594
|
|
|
595
|
+
# Command path to list, from words that may include positional arguments, as on a command line.
|
|
596
|
+
# e.g. `faspex5 packages receive ALL` lists `faspex5 packages receive`.
|
|
597
|
+
# @param name [Symbol] plugin name
|
|
598
|
+
# @param reg [CommandRegistry] registry of plugin
|
|
599
|
+
# @param words [Array<Symbol>] sub-commands (or aliases), possibly mixed with their arguments
|
|
600
|
+
# @return [Array<Symbol>] command path
|
|
601
|
+
# @raise [Cli::BadArgument] if a word is neither a sub-command nor an expected argument
|
|
602
|
+
def listed_command_path(name, reg, words)
|
|
603
|
+
path, unknown = reg.command_path(words)
|
|
604
|
+
raise Cli::BadArgument, Parser.multi_choice_assert_msg("no such command: #{[name, *path, unknown].join(' ')}", reg.children_of(path).keys) unless unknown.nil?
|
|
605
|
+
path
|
|
606
|
+
end
|
|
607
|
+
|
|
608
|
+
# Build syntax by interleaving each path segment with the arguments declared on that node
|
|
609
|
+
# @param name [String, Symbol] plugin name
|
|
610
|
+
# @param reg [CommandRegistry] registry of plugin
|
|
611
|
+
# @param path [Array<Symbol>] command path
|
|
612
|
+
# @return [String] e.g. `node access_keys do <access_key_id> ls <path>`
|
|
613
|
+
def command_syntax(name, reg, path)
|
|
614
|
+
tokens = [name.to_s]
|
|
615
|
+
path.each_index do |i|
|
|
616
|
+
tokens << path[i].to_s
|
|
617
|
+
reg.arguments_at(path[0, i + 1]).each { |a| tokens << a.syntax }
|
|
618
|
+
end
|
|
619
|
+
tokens.join(' ')
|
|
620
|
+
end
|
|
621
|
+
|
|
546
622
|
def action_options(plugin_name:, **)
|
|
547
623
|
# Instantiate the plugin so that it registers all its options in context.options
|
|
548
624
|
Plugins::Factory.instance.create(plugin_name.to_sym, context: context)
|
|
549
625
|
rows = context.options.declared_options.map do |sym, opt|
|
|
550
626
|
row = {
|
|
551
|
-
option
|
|
552
|
-
description
|
|
627
|
+
'option' => "--#{sym.to_s.tr('_', '-')}",
|
|
628
|
+
'description' => opt.description.to_s
|
|
553
629
|
}
|
|
554
|
-
row[
|
|
555
|
-
row[
|
|
630
|
+
row['allowed'] = opt.allowed_info unless opt.allowed_info.nil?
|
|
631
|
+
row['deprecated'] = opt.deprecation.last if opt.deprecation
|
|
632
|
+
row['replacement'] = opt.deprecation.message if opt.deprecation
|
|
556
633
|
row
|
|
557
634
|
end
|
|
558
|
-
Result::ObjectList.new(rows, fields: %w[option description allowed deprecated])
|
|
559
|
-
end
|
|
560
|
-
|
|
561
|
-
def action_test_throw(exception_class_name:, exception_text:, **)
|
|
562
|
-
type = Object.const_get(exception_class_name)
|
|
563
|
-
Aspera.assert(type <= Exception) { "#{type} is not an exception: #{type.class}" }
|
|
564
|
-
raise type, exception_text
|
|
635
|
+
Result::ObjectList.new(rows, fields: %w[option description allowed deprecated replacement])
|
|
565
636
|
end
|
|
566
637
|
|
|
567
|
-
def
|
|
638
|
+
def action_completion_words(words: nil, **)
|
|
568
639
|
if words.nil? || words.empty?
|
|
569
640
|
# Level 0: propose plugin names
|
|
570
641
|
Plugins::Factory.instance.plugin_list.each { |p| puts p }
|
|
@@ -582,6 +653,14 @@ module Aspera
|
|
|
582
653
|
Process.exit(0)
|
|
583
654
|
end
|
|
584
655
|
|
|
656
|
+
# Display the completion script for the given shell.
|
|
657
|
+
# Raw output, not formatted, so that it can be evaluated by the shell.
|
|
658
|
+
# @param shell [Symbol] One of `COMPLETION_SHELLS`
|
|
659
|
+
def completion_script(shell)
|
|
660
|
+
$stdout.write(File.read(File.join(COMPLETION_FOLDER, "#{Info::CMD_NAME}.#{shell}")))
|
|
661
|
+
Process.exit(0)
|
|
662
|
+
end
|
|
663
|
+
|
|
585
664
|
# Folder in $HOME for application files (~/.aspera)
|
|
586
665
|
ASPERA_HOME_FOLDER_NAME = '.aspera'
|
|
587
666
|
# Default config file name
|