aspera-cli 4.27.2 → 4.27.3
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 +50 -0
- data/bin/ascli +2 -1
- data/docs/README.md +804 -746
- data/lib/aspera/agent/connect.rb +6 -4
- data/lib/aspera/agent/desktop.rb +2 -2
- data/lib/aspera/agent/direct.rb +3 -1
- data/lib/aspera/agent/node.rb +3 -3
- 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 +9 -7
- data/lib/aspera/api/httpgw.rb +37 -33
- data/lib/aspera/api/node.rb +38 -33
- data/lib/aspera/ascmd.rb +3 -1
- data/lib/aspera/ascp/installation.rb +62 -27
- 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 +2 -2
- data/lib/aspera/cli/bootstrapper.rb +11 -15
- data/lib/aspera/cli/command_line.rb +252 -0
- data/lib/aspera/cli/command_registry.rb +149 -33
- data/lib/aspera/cli/command_spec.rb +103 -14
- 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 +2 -0
- data/lib/aspera/cli/formatter.rb +87 -75
- data/lib/aspera/cli/gem_checker.rb +1 -1
- data/lib/aspera/cli/hints.rb +7 -6
- data/lib/aspera/cli/http.rb +21 -21
- 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 +103 -0
- data/lib/aspera/cli/option_value.rb +281 -0
- data/lib/aspera/cli/options.schema.yaml +38 -5
- data/lib/aspera/cli/parser.rb +307 -848
- data/lib/aspera/cli/plugins/alee.rb +7 -4
- data/lib/aspera/cli/plugins/aoc.rb +430 -380
- data/lib/aspera/cli/plugins/ats.rb +58 -73
- data/lib/aspera/cli/plugins/base.rb +190 -240
- data/lib/aspera/cli/plugins/basic_auth.rb +2 -10
- data/lib/aspera/cli/plugins/config.rb +244 -178
- data/lib/aspera/cli/plugins/console.rb +102 -38
- 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 +176 -173
- data/lib/aspera/cli/plugins/faspio.rb +5 -10
- 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 +277 -311
- data/lib/aspera/cli/plugins/orchestrator.rb +90 -77
- data/lib/aspera/cli/plugins/preview.rb +79 -90
- data/lib/aspera/cli/plugins/server.rb +76 -50
- data/lib/aspera/cli/plugins/shares.rb +68 -116
- data/lib/aspera/cli/preset_actions.rb +17 -10
- data/lib/aspera/cli/preset_manager.rb +12 -2
- data/lib/aspera/cli/prompt.rb +35 -0
- data/lib/aspera/cli/result.rb +13 -18
- data/lib/aspera/cli/runner.rb +31 -54
- data/lib/aspera/cli/special_values.rb +5 -0
- data/lib/aspera/cli/sync_actions.rb +41 -37
- data/lib/aspera/cli/terminal_formatter.rb +9 -3
- data/lib/aspera/cli/transfer_actions.rb +0 -6
- data/lib/aspera/cli/transfer_agent.rb +29 -35
- data/lib/aspera/cli/vault_manager.rb +0 -17
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/cli/wizard.rb +4 -2
- data/lib/aspera/coverage.rb +1 -0
- data/lib/aspera/environment.rb +6 -0
- 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 +1 -1
- data/lib/aspera/keychain/one_password_api.rb +1 -1
- data/lib/aspera/link_header.rb +2 -2
- data/lib/aspera/log.rb +22 -25
- data/lib/aspera/markdown.rb +2 -0
- data/lib/aspera/mime.rb +25 -0
- data/lib/aspera/node_simulator.rb +1 -0
- data/lib/aspera/oauth/base.rb +35 -25
- data/lib/aspera/oauth/factory.rb +1 -0
- data/lib/aspera/oauth/generic.rb +1 -1
- data/lib/aspera/oauth/jwt.rb +1 -1
- data/lib/aspera/oauth/web.rb +9 -8
- data/lib/aspera/preview/file_types.rb +4 -4
- data/lib/aspera/preview/generator.rb +7 -0
- data/lib/aspera/preview/options.rb +4 -4
- 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 +60 -0
- data/lib/aspera/rest/call_error.rb +27 -0
- data/lib/aspera/rest/client.rb +514 -0
- data/lib/aspera/rest/error_analyzer.rb +113 -0
- data/lib/aspera/rest/list.rb +143 -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 on Cloud Automation API-1.0.5-enhanced.yaml +2395 -0
- data/lib/aspera/schema/documentation.rb +13 -3
- data/lib/aspera/schema/registry.rb +18 -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 +39 -7
- metadata.gz.sig +0 -0
- data/lib/aspera/colors.rb +0 -79
- 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
|
@@ -30,6 +30,7 @@ require 'aspera/rest'
|
|
|
30
30
|
require 'aspera/oauth/jwt'
|
|
31
31
|
require 'aspera/log'
|
|
32
32
|
require 'aspera/assert'
|
|
33
|
+
require 'aspera/string_ext'
|
|
33
34
|
require 'openssl'
|
|
34
35
|
require 'digest'
|
|
35
36
|
require 'open3'
|
|
@@ -81,66 +82,67 @@ module Aspera
|
|
|
81
82
|
end
|
|
82
83
|
|
|
83
84
|
DEFAULT_CHECK_NEW_VERSION_DAYS = 7
|
|
84
|
-
|
|
85
|
+
# Shells with a completion script in COMPLETION_FOLDER
|
|
86
|
+
COMPLETION_SHELLS = %i[bash zsh fish].freeze
|
|
87
|
+
COMPLETION_FOLDER = File.expand_path('../completion', __dir__)
|
|
88
|
+
private_constant :DEFAULT_CHECK_NEW_VERSION_DAYS, :COMPLETION_SHELLS, :COMPLETION_FOLDER
|
|
85
89
|
|
|
86
|
-
option :preset, description: 'Load the named option preset from current config file', short: 'P',
|
|
90
|
+
option :preset, description: 'Load the named option preset from current config file', short: 'P', on_set: :load_preset
|
|
87
91
|
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',
|
|
92
|
+
option :plugin_folder, description: 'Folder where to find additional plugins', on_set: :add_plugin_folders
|
|
89
93
|
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:")',
|
|
94
|
+
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=)
|
|
95
|
+
option :sdk_folder, description: 'Ascp: Path to folder with ascp (or product with "product:")', on_set: Ascp::Installation.instance.method(:sdk_folder=)
|
|
92
96
|
option :smtp, schema: Schema::Registry::SMTP_OPTIONS
|
|
93
97
|
option :notify_to, description: 'Email: Recipient for notification of transfers'
|
|
94
98
|
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
|
|
99
|
+
option :cache_tokens, description: 'Save and reuse OAuth tokens', allowed: Type::BOOLEAN, default: true
|
|
100
|
+
option :expand_mounts, description: 'Commands: list commands of sub-trees provided by another plugin', allowed: Type::BOOLEAN, default: false
|
|
101
|
+
option :no_default, description: 'Do not load default configuration for plugin', allowed: Type::NONE, short: 'N', on_set: -> { presets.use_plugin_defaults = false }
|
|
96
102
|
|
|
97
103
|
def initialize(**_)
|
|
98
104
|
super
|
|
99
105
|
@vault_instance = nil
|
|
100
106
|
@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
107
|
# Declare wizard options (Wizard#initialize calls options.declare internally)
|
|
105
108
|
@wizard = Wizard.new(self, context.main_folder)
|
|
106
|
-
options.parse_options!
|
|
107
|
-
set_sdk_dir
|
|
108
109
|
# HTTP options: declare metadata (class method), then bind to the instance
|
|
109
110
|
Http.declare_options(options)
|
|
110
111
|
context.http_config.bind_options(options)
|
|
112
|
+
# Values set through `on_set` callbacks are used below (sdk_folder)
|
|
111
113
|
options.parse_options!
|
|
114
|
+
set_sdk_dir
|
|
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,162 @@ 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
|
+
@wizard.find(apps, preset_name: preset_name)
|
|
190
|
+
end
|
|
191
|
+
)
|
|
192
|
+
command :coffee, description: 'Show a coffee image', action: ->(**) { Result::Image.new(COFFEE_IMAGE_URL) }
|
|
193
|
+
command :image, description: 'Show an image',
|
|
179
194
|
arguments: [{name: :image_uri, type: nil}],
|
|
180
195
|
action: ->(image_uri:, **) { Result::Image.new(image_uri) }
|
|
181
196
|
command :sync, description: 'Manage Aspera Sync operations'
|
|
182
|
-
command :gem, description: '
|
|
183
|
-
command :folder, description: '
|
|
184
|
-
command :file, description: '
|
|
197
|
+
command :gem, description: 'Show gem information'
|
|
198
|
+
command :folder, description: 'Show the configuration folder path', action: ->(**) { Result::Text.new(context.main_folder) }
|
|
199
|
+
command :file, description: 'Show the configuration file path', action: ->(**) { Result::Text.new(context.presets.config_file) }
|
|
185
200
|
command(
|
|
186
201
|
:email_test, description: 'Send a test email',
|
|
187
|
-
action: lambda do
|
|
202
|
+
action: lambda do |**|
|
|
188
203
|
context.mailer.send_email_template(email_template_default: EMAIL_TEST_TEMPLATE)
|
|
189
204
|
Result::Nothing.new
|
|
190
205
|
end
|
|
191
206
|
)
|
|
192
|
-
command :smtp_settings, description: '
|
|
207
|
+
command :smtp_settings, description: 'Show the current SMTP settings', action: ->(**) { Result::SingleObject.new(context.mailer.email_settings) }
|
|
193
208
|
command(
|
|
194
209
|
:proxy_check, description: 'Check the proxy returned by the PAC script for a given URL',
|
|
195
|
-
arguments: [{name: :server_url
|
|
210
|
+
arguments: [{name: :server_url}],
|
|
196
211
|
action: lambda do |server_url:, **|
|
|
197
212
|
Aspera.assert(!context.pac_executor.nil?, type: Cli::BadArgument) { 'No PAC script configured, use --fpac' }
|
|
198
213
|
Result::ValueList.new(context.pac_executor.get_proxies(server_url), name: 'proxy')
|
|
199
214
|
end
|
|
200
215
|
)
|
|
201
|
-
command :check_update, description: 'Check if a newer version of the gem is available', action: -> { Result::SingleObject.new(check_gem_version) }
|
|
216
|
+
command :check_update, description: 'Check if a newer version of the gem is available', action: ->(**) { Result::SingleObject.new(check_gem_version) }
|
|
202
217
|
command :initdemo, description: 'Initialize the demo server preset'
|
|
203
218
|
command :vault, description: 'Manage secrets in the vault'
|
|
204
219
|
commands_under :vault do
|
|
205
|
-
command :info, description: '
|
|
220
|
+
command :info, description: 'Show vault information',
|
|
206
221
|
action: ->(**) { Result::SingleObject.new(vault_required.info) }
|
|
207
222
|
command :ids, description: 'List secret labels in the vault',
|
|
208
223
|
action: ->(**) { Result::ObjectList.new(vault_required.ids) }
|
|
209
224
|
command :list, description: 'List all secrets with full details',
|
|
210
225
|
action: ->(**) { Result::ObjectList.new(vault_required.all) }
|
|
211
|
-
command
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
226
|
+
command(
|
|
227
|
+
:show, description: 'Show a secret by label (or id)',
|
|
228
|
+
arguments: [{name: :label}, {name: :id, mandatory: false, default: nil}],
|
|
229
|
+
action: lambda do |label:, id: nil, **|
|
|
230
|
+
v = vault_required
|
|
231
|
+
kwargs = id && v.method(:get).parameters.any? { |_t, n| n == :id } ? {id: id} : {}
|
|
232
|
+
Result::SingleObject.new(v.get(label: label, **kwargs))
|
|
233
|
+
end
|
|
234
|
+
)
|
|
235
|
+
command(
|
|
236
|
+
:create, description: 'Add a new secret to the vault',
|
|
237
|
+
arguments: [{name: :secret, type: Hash, schema: Schema::Registry::VAULT_SECRET}],
|
|
238
|
+
action: lambda do |secret:, **|
|
|
239
|
+
vault_required.set(secret.symbolize_keys)
|
|
240
|
+
Result::Status.new('Secret added')
|
|
241
|
+
end
|
|
242
|
+
)
|
|
243
|
+
command :delete, description: 'Delete a secret by label (or id)',
|
|
244
|
+
arguments: [{name: :label}, {name: :id, mandatory: false, default: nil}]
|
|
245
|
+
command(
|
|
246
|
+
:password, description: 'Change the vault password',
|
|
247
|
+
arguments: [{name: :new_password}],
|
|
248
|
+
action: lambda do |new_password:, **|
|
|
249
|
+
Aspera.assert(vault_required.respond_to?(:change_password), 'Vault does not support password change')
|
|
250
|
+
vault_required.change_password(new_password)
|
|
251
|
+
Result::Status.new('Vault password updated')
|
|
252
|
+
end
|
|
253
|
+
)
|
|
254
|
+
command :import, description: 'Import secrets from a JSON array (supports --bulk)',
|
|
220
255
|
arguments: [{name: :secrets, type: Array, schema: {type: 'array', items: {'$ref' => Schema::Registry::VAULT_SECRET}}}]
|
|
221
256
|
end
|
|
222
|
-
command :commands, description: 'List all available commands
|
|
257
|
+
command :commands, description: 'List all available commands, of all plugins or only the given one, optionally under a command path',
|
|
258
|
+
arguments: [{name: :plugin_name, mandatory: false, default: nil},
|
|
259
|
+
{name: :command_path, multiple: true, mandatory: false, default: nil}]
|
|
223
260
|
command :options, description: 'List all options available for a plugin',
|
|
224
|
-
arguments: [{name: :plugin_name
|
|
261
|
+
arguments: [{name: :plugin_name}]
|
|
225
262
|
command :test, description: 'Internal test commands'
|
|
226
|
-
command :platform, description: '
|
|
227
|
-
command :completion, description: '
|
|
263
|
+
command :platform, description: 'Show the current platform/architecture', action: ->(**) { Result::Text.new(Environment.instance.architecture) }
|
|
264
|
+
command :completion, description: 'Shell completion: activation scripts and next words'
|
|
228
265
|
|
|
229
266
|
# remote_certificate sub-commands
|
|
230
267
|
commands_under :remote_certificate do
|
|
231
|
-
command
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
268
|
+
command(
|
|
269
|
+
:chain, description: 'Show the full certificate chain as PEM',
|
|
270
|
+
arguments: [{name: :remote_url}],
|
|
271
|
+
action: lambda do |remote_url:, **|
|
|
272
|
+
remote_chain = Rest.remote_certificate_chain(remote_url, as_string: false)
|
|
273
|
+
Aspera.assert(remote_chain&.first) { "No certificate found for #{remote_url}" }
|
|
274
|
+
Result::Text.new(remote_chain.map(&:to_pem).join("\n"))
|
|
275
|
+
end
|
|
276
|
+
)
|
|
277
|
+
command(
|
|
278
|
+
:only, description: 'Show only the server certificate as PEM',
|
|
279
|
+
arguments: [{name: :remote_url}],
|
|
280
|
+
action: lambda do |remote_url:, **|
|
|
281
|
+
remote_chain = Rest.remote_certificate_chain(remote_url, as_string: false)
|
|
282
|
+
Aspera.assert(remote_chain&.first) { "No certificate found for #{remote_url}" }
|
|
283
|
+
Result::Text.new(remote_chain.first.to_pem)
|
|
284
|
+
end
|
|
285
|
+
)
|
|
286
|
+
command(
|
|
287
|
+
:name, description: 'Show the CN of the server certificate',
|
|
288
|
+
arguments: [{name: :remote_url}],
|
|
289
|
+
action: lambda do |remote_url:, **|
|
|
290
|
+
remote_chain = Rest.remote_certificate_chain(remote_url, as_string: false)
|
|
291
|
+
Aspera.assert(remote_chain&.first) { "No certificate found for #{remote_url}" }
|
|
292
|
+
Result::Text.new(remote_chain.first.subject.to_a.find { |name, _, _| name == 'CN' }[1])
|
|
293
|
+
end
|
|
294
|
+
)
|
|
237
295
|
end
|
|
238
296
|
|
|
239
297
|
# tokens sub-commands
|
|
240
298
|
commands_under :tokens do
|
|
241
299
|
command(
|
|
242
300
|
:flush, description: 'Delete all cached OAuth tokens',
|
|
243
|
-
action: lambda do
|
|
301
|
+
action: lambda do |**|
|
|
244
302
|
require 'aspera/api/node'
|
|
245
303
|
Result::ValueList.new(OAuth::Factory.instance.flush_tokens, name: 'file')
|
|
246
304
|
end
|
|
247
305
|
)
|
|
248
306
|
command(
|
|
249
307
|
:list, description: 'List all cached OAuth tokens',
|
|
250
|
-
action: lambda do
|
|
308
|
+
action: lambda do |**|
|
|
251
309
|
require 'aspera/api/node'
|
|
252
310
|
Result::ObjectList.new(OAuth::Factory.instance.persisted_tokens)
|
|
253
311
|
end
|
|
@@ -261,25 +319,33 @@ module Aspera
|
|
|
261
319
|
command :list, description: 'List all available plugins'
|
|
262
320
|
command :create, description: 'Create a new plugin skeleton file',
|
|
263
321
|
arguments: [
|
|
264
|
-
{name: :name
|
|
265
|
-
{name: :folder,
|
|
322
|
+
{name: :name},
|
|
323
|
+
{name: :folder, mandatory: false}
|
|
266
324
|
]
|
|
267
325
|
end
|
|
268
326
|
|
|
269
327
|
# ascp sub-commands
|
|
270
328
|
command :ascp, description: 'Manage FASP/ascp transfer engine'
|
|
271
329
|
commands_under :ascp do
|
|
272
|
-
command :show, description: '
|
|
273
|
-
command :info, description: '
|
|
330
|
+
command :show, description: 'Show ascp binary path', action: ->(**) { Result::Text.new(Ascp::Installation.instance.path(:ascp)) }
|
|
331
|
+
command :info, description: 'Show ascp and transfer spec information'
|
|
274
332
|
command :install, description: 'Install the transfer SDK',
|
|
275
|
-
arguments: [{name: :version, mandatory: false, default: nil}]
|
|
276
|
-
|
|
277
|
-
command
|
|
278
|
-
|
|
279
|
-
|
|
333
|
+
arguments: [{name: :version, mandatory: false, default: nil}],
|
|
334
|
+
action: ->(version: nil, **) { install_transfer_sdk(version: version) }
|
|
335
|
+
command(
|
|
336
|
+
:spec, description: 'Show the transfer spec schema',
|
|
337
|
+
action: lambda do |**|
|
|
338
|
+
builder = Schema::Documentation.new(TerminalFormatter, Transfer::Spec::SCHEMA, include_option: true, agent_columns: true).build
|
|
339
|
+
Result::ObjectList.new(builder.rows, fields: builder.columns)
|
|
340
|
+
end
|
|
341
|
+
)
|
|
342
|
+
command :schema, description: 'Show the transfer spec JSON schema',
|
|
343
|
+
arguments: [{name: :agent_name, allowed: Agent::Factory::ALL.keys, mandatory: false, default: nil}]
|
|
344
|
+
command :errors, description: 'Show FASP error codes'
|
|
280
345
|
command :products, description: 'Manage installed Aspera products'
|
|
281
346
|
commands_under :products do
|
|
282
|
-
command :list, description: 'List installed Aspera products'
|
|
347
|
+
command :list, description: 'List installed Aspera products',
|
|
348
|
+
action: ->(**) { Result::ObjectList.new(Ascp::Installation.instance.installed_products, fields: %w[name app_root]) }
|
|
283
349
|
end
|
|
284
350
|
end
|
|
285
351
|
|
|
@@ -296,24 +362,36 @@ module Aspera
|
|
|
296
362
|
# transfer async management sub-commands
|
|
297
363
|
command :transfer, description: 'Manage asynchronous transfers'
|
|
298
364
|
commands_under :transfer do
|
|
299
|
-
command
|
|
365
|
+
command(
|
|
366
|
+
:list, description: 'List all async transfer jobs',
|
|
367
|
+
action: lambda do |**|
|
|
368
|
+
rows = async_transfer_store.list
|
|
369
|
+
Result::ObjectList.new(rows, fields: %w[job_id agent_type status started_at ended_at bytes_transferred transfer_id])
|
|
370
|
+
end
|
|
371
|
+
)
|
|
300
372
|
command :status, description: 'Show status of an async transfer job',
|
|
301
|
-
arguments: [{name: :job_id
|
|
373
|
+
arguments: [{name: :job_id}]
|
|
302
374
|
command :cleanup, description: 'Remove completed/failed/cancelled transfer entries'
|
|
303
375
|
end
|
|
304
376
|
|
|
305
377
|
# transferd sub-commands
|
|
306
378
|
command :transferd, description: 'Manage the transfer daemon (transferd)'
|
|
307
379
|
commands_under :transferd do
|
|
308
|
-
command :install, description: 'Install the transfer daemon'
|
|
309
|
-
command
|
|
380
|
+
command :install, description: 'Install the transfer daemon', action: ->(**) { install_transfer_sdk }
|
|
381
|
+
command(
|
|
382
|
+
:list, description: 'List available SDK locations',
|
|
383
|
+
action: lambda do |**|
|
|
384
|
+
sdk_list = Ascp::Installation.instance.sdk_locations
|
|
385
|
+
Result::ObjectList.new(sdk_list, fields: sdk_list.first.keys - ['url'])
|
|
386
|
+
end
|
|
387
|
+
)
|
|
310
388
|
end
|
|
311
389
|
|
|
312
390
|
# sync sub-commands
|
|
313
391
|
commands_under :sync do
|
|
314
392
|
command(
|
|
315
|
-
:spec, description: '
|
|
316
|
-
action: lambda do
|
|
393
|
+
:spec, description: 'Show the sync configuration schema',
|
|
394
|
+
action: lambda do |**|
|
|
317
395
|
builder = Schema::Documentation.new(TerminalFormatter, Sync::Operations::CONF_SCHEMA, include_option: true).build
|
|
318
396
|
Result::ObjectList.new(builder.rows, fields: builder.columns)
|
|
319
397
|
end
|
|
@@ -321,50 +399,57 @@ module Aspera
|
|
|
321
399
|
command :admin, description: 'Manage sync database (admin operations)'
|
|
322
400
|
SyncActions.register_sync_admin_commands(self, :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
496
|
file_dest = File.join(transfer.destination_folder(Transfer::Spec::DIRECTION_RECEIVE), file_url.gsub(%r{.*/}, '')) 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,66 @@ 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
|
+
prefix = Array(command_path).map(&:to_sym)
|
|
573
|
+
Aspera.assert(prefix.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
|
+
Aspera.assert(prefix.empty? || reg[prefix], type: Cli::BadArgument) { "no such command: #{name} #{prefix.join(' ')}" }
|
|
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
|
+
# Build syntax by interleaving each path segment with the arguments declared on that node
|
|
596
|
+
# @param name [String, Symbol] plugin name
|
|
597
|
+
# @param reg [CommandRegistry] registry of plugin
|
|
598
|
+
# @param path [Array<Symbol>] command path
|
|
599
|
+
# @return [String] e.g. `node access_keys do <access_key_id> ls <path>`
|
|
600
|
+
def command_syntax(name, reg, path)
|
|
601
|
+
tokens = [name.to_s]
|
|
602
|
+
path.each_index do |i|
|
|
603
|
+
tokens << path[i].to_s
|
|
604
|
+
reg.arguments_at(path[0, i + 1]).each { |a| tokens << a.syntax }
|
|
605
|
+
end
|
|
606
|
+
tokens.join(' ')
|
|
607
|
+
end
|
|
608
|
+
|
|
546
609
|
def action_options(plugin_name:, **)
|
|
547
610
|
# Instantiate the plugin so that it registers all its options in context.options
|
|
548
611
|
Plugins::Factory.instance.create(plugin_name.to_sym, context: context)
|
|
549
612
|
rows = context.options.declared_options.map do |sym, opt|
|
|
550
613
|
row = {
|
|
551
|
-
option
|
|
552
|
-
description
|
|
614
|
+
'option' => "--#{sym.to_s.tr('_', '-')}",
|
|
615
|
+
'description' => opt.description.to_s
|
|
553
616
|
}
|
|
554
|
-
row[
|
|
555
|
-
row[
|
|
617
|
+
row['allowed'] = opt.allowed_info unless opt.allowed_info.nil?
|
|
618
|
+
row['deprecated'] = opt.deprecation.last if opt.deprecation
|
|
619
|
+
row['replacement'] = opt.deprecation.message if opt.deprecation
|
|
556
620
|
row
|
|
557
621
|
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
|
|
622
|
+
Result::ObjectList.new(rows, fields: %w[option description allowed deprecated replacement])
|
|
565
623
|
end
|
|
566
624
|
|
|
567
|
-
def
|
|
625
|
+
def action_completion_words(words: nil, **)
|
|
568
626
|
if words.nil? || words.empty?
|
|
569
627
|
# Level 0: propose plugin names
|
|
570
628
|
Plugins::Factory.instance.plugin_list.each { |p| puts p }
|
|
@@ -582,6 +640,14 @@ module Aspera
|
|
|
582
640
|
Process.exit(0)
|
|
583
641
|
end
|
|
584
642
|
|
|
643
|
+
# Display the completion script for the given shell.
|
|
644
|
+
# Raw output, not formatted, so that it can be evaluated by the shell.
|
|
645
|
+
# @param shell [Symbol] One of `COMPLETION_SHELLS`
|
|
646
|
+
def completion_script(shell)
|
|
647
|
+
$stdout.write(File.read(File.join(COMPLETION_FOLDER, "#{Info::CMD_NAME}.#{shell}")))
|
|
648
|
+
Process.exit(0)
|
|
649
|
+
end
|
|
650
|
+
|
|
585
651
|
# Folder in $HOME for application files (~/.aspera)
|
|
586
652
|
ASPERA_HOME_FOLDER_NAME = '.aspera'
|
|
587
653
|
# Default config file name
|