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 initdemo genkey pubkey asperasoft filelists
|
|
4
|
+
require 'aspera/cli/bootstrapper'
|
|
4
5
|
require 'aspera/cli/plugins/basic_auth'
|
|
5
6
|
require 'aspera/cli/plugins/factory'
|
|
6
7
|
require 'aspera/cli/extended_value'
|
|
@@ -8,36 +9,30 @@ require 'aspera/cli/special_values'
|
|
|
8
9
|
require 'aspera/cli/version'
|
|
9
10
|
require 'aspera/cli/formatter'
|
|
10
11
|
require 'aspera/cli/info'
|
|
11
|
-
require 'aspera/cli/transfer_progress'
|
|
12
12
|
require 'aspera/cli/wizard'
|
|
13
13
|
require 'aspera/cli/sync_actions'
|
|
14
|
-
require 'aspera/cli/
|
|
15
|
-
require 'aspera/cli/
|
|
14
|
+
require 'aspera/cli/ascp_actions'
|
|
15
|
+
require 'aspera/cli/preset_actions'
|
|
16
|
+
require 'aspera/cli/vault_manager'
|
|
17
|
+
require 'aspera/cli/transfer_actions'
|
|
18
|
+
require 'aspera/cli/gem_checker'
|
|
16
19
|
require 'aspera/ascp/installation'
|
|
17
20
|
require 'aspera/sync/operations'
|
|
18
21
|
require 'aspera/products/transferd'
|
|
19
|
-
require 'aspera/transfer/parameters'
|
|
20
22
|
require 'aspera/transfer/spec'
|
|
21
23
|
require 'aspera/schema/documentation'
|
|
22
24
|
require 'aspera/keychain/macos_security'
|
|
23
|
-
require 'aspera/proxy_auto_config'
|
|
24
25
|
require 'aspera/environment'
|
|
25
26
|
require 'aspera/persistency_action_once'
|
|
26
27
|
require 'aspera/id_generator'
|
|
27
|
-
require 'aspera/persistency_folder'
|
|
28
28
|
require 'aspera/data_repository'
|
|
29
|
-
require 'aspera/line_logger'
|
|
30
29
|
require 'aspera/rest'
|
|
31
30
|
require 'aspera/oauth/jwt'
|
|
32
31
|
require 'aspera/log'
|
|
33
32
|
require 'aspera/assert'
|
|
34
|
-
require 'aspera/oauth'
|
|
35
|
-
require 'aspera/ssl'
|
|
36
33
|
require 'openssl'
|
|
37
34
|
require 'digest'
|
|
38
35
|
require 'open3'
|
|
39
|
-
require 'date'
|
|
40
|
-
require 'erb'
|
|
41
36
|
require 'net/http'
|
|
42
37
|
|
|
43
38
|
module Aspera
|
|
@@ -46,6 +41,11 @@ module Aspera
|
|
|
46
41
|
# Manage the CLI config file
|
|
47
42
|
class Config < Base
|
|
48
43
|
include SyncActions
|
|
44
|
+
include VaultManager
|
|
45
|
+
include GemChecker
|
|
46
|
+
include AscpActions
|
|
47
|
+
include PresetActions
|
|
48
|
+
include TransferActions
|
|
49
49
|
|
|
50
50
|
class << self
|
|
51
51
|
# Folder containing plugins in the gem's main folder
|
|
@@ -53,7 +53,7 @@ module Aspera
|
|
|
53
53
|
File.dirname(File.expand_path(__FILE__))
|
|
54
54
|
end
|
|
55
55
|
|
|
56
|
-
# @return main folder where code is, i.e. .../lib
|
|
56
|
+
# @return [String] main folder where code is, i.e. .../lib
|
|
57
57
|
# Go up as many times as englobing modules (not counting class, as it is a file)
|
|
58
58
|
def gem_src_root
|
|
59
59
|
# Module.nesting[2] is Cli::Plugins
|
|
@@ -65,10 +65,10 @@ module Aspera
|
|
|
65
65
|
return Marshal.load(Marshal.dump(val))
|
|
66
66
|
end
|
|
67
67
|
|
|
68
|
-
# @return product family folder (~/.aspera)
|
|
68
|
+
# @return [String] product family folder (~/.aspera)
|
|
69
69
|
def module_family_folder
|
|
70
70
|
user_home_folder = Dir.home
|
|
71
|
-
Aspera.assert(Dir.exist?(user_home_folder), type: Cli::Error){"Home folder does not exist: #{user_home_folder}. Check your user environment."}
|
|
71
|
+
Aspera.assert(Dir.exist?(user_home_folder), type: Cli::Error) { "Home folder does not exist: #{user_home_folder}. Check your user environment." }
|
|
72
72
|
return File.join(user_home_folder, ASPERA_HOME_FOLDER_NAME)
|
|
73
73
|
end
|
|
74
74
|
|
|
@@ -80,262 +80,282 @@ module Aspera
|
|
|
80
80
|
end
|
|
81
81
|
end
|
|
82
82
|
|
|
83
|
+
DEFAULT_CHECK_NEW_VERSION_DAYS = 7
|
|
84
|
+
private_constant :DEFAULT_CHECK_NEW_VERSION_DAYS
|
|
85
|
+
|
|
86
|
+
option :preset, description: 'Load the named option preset from current config file', short: 'P', handler: :option_preset
|
|
87
|
+
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', handler: :option_plugin_folder
|
|
89
|
+
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', handler: {o: Ascp::Installation.instance, m: :transferd_urls}
|
|
91
|
+
option :sdk_folder, description: 'Ascp: Path to folder with ascp (or product with "product:")', handler: {o: Products::Transferd, m: :sdk_directory}
|
|
92
|
+
option :smtp, schema: Schema::Registry::SMTP_OPTIONS
|
|
93
|
+
option :notify_to, description: 'Email: Recipient for notification of transfers'
|
|
94
|
+
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, handler: :option_cache_tokens
|
|
96
|
+
|
|
83
97
|
def initialize(**_)
|
|
84
|
-
# We need to defer parsing of options until we have the config file, so we can use @extend with @preset
|
|
85
98
|
super
|
|
86
99
|
@vault_instance = nil
|
|
87
|
-
@pac_exec = nil
|
|
88
100
|
@sdk_default_location = false
|
|
89
101
|
@option_cache_tokens = true
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
options.declare(
|
|
95
|
-
:home, 'Home folder for tool',
|
|
96
|
-
handler: {o: self, m: :main_folder},
|
|
97
|
-
default: self.class.default_app_main_folder(app_name: Info::CMD_NAME)
|
|
98
|
-
)
|
|
99
|
-
options.parse_options!
|
|
100
|
-
Log.log.debug{"#{Info::CMD_NAME} folder: #{@main_folder}"}
|
|
101
|
-
setup_persistency_and_plugin_folders
|
|
102
|
-
# Option to set config file
|
|
103
|
-
options.declare(
|
|
104
|
-
:config_file, 'Path to YAML file with preset configuration',
|
|
105
|
-
handler: {o: self, m: :option_config_file},
|
|
106
|
-
default: File.join(@main_folder, DEFAULT_CONFIG_FILENAME)
|
|
107
|
-
)
|
|
108
|
-
options.parse_options!
|
|
109
|
-
# Instantiate PresetManager (reads config file) and inject into context
|
|
110
|
-
context.presets = PresetManager.new(config_file: @option_config_file)
|
|
111
|
-
# Instantiate Http and inject into context
|
|
112
|
-
context.http_config = Http.new
|
|
113
|
-
setup_extended_value_handlers
|
|
114
|
-
# Vault options
|
|
115
|
-
options.declare(:secret, 'Secret for access keys')
|
|
116
|
-
options.declare(:vault, 'Vault for secrets', allowed: Hash)
|
|
117
|
-
options.declare(:vault_password, 'Vault password')
|
|
118
|
-
options.parse_options!
|
|
119
|
-
# Declare generic plugin options only after handlers are declared
|
|
120
|
-
Base.declare_options(options)
|
|
121
|
-
# Configuration options
|
|
122
|
-
options.declare(:no_default, 'Do not load default configuration for plugin', allowed: Allowed::TYPES_NONE, short: 'N'){ presets.use_plugin_defaults = false }
|
|
123
|
-
options.declare(:preset, 'Load the named option preset from current config file', short: 'P', handler: {o: self, m: :option_preset})
|
|
124
|
-
options.declare(:version_check_days, 'Period in days to check new version (zero to disable)', allowed: Allowed::TYPES_INTEGER, default: DEFAULT_CHECK_NEW_VERSION_DAYS)
|
|
125
|
-
options.declare(:plugin_folder, 'Folder where to find additional plugins', handler: {o: self, m: :option_plugin_folder})
|
|
126
|
-
# Declare wizard options
|
|
127
|
-
@wizard = Wizard.new(self, @main_folder)
|
|
128
|
-
# Transfer SDK options
|
|
129
|
-
options.declare(:sdk_url, 'Ascp: URL to get Aspera Transfer Executables', default: SpecialValues::DEF)
|
|
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
|
+
@wizard = Wizard.new(self, context.main_folder)
|
|
130
106
|
options.parse_options!
|
|
131
107
|
set_sdk_dir
|
|
132
|
-
options
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
# Email options
|
|
136
|
-
options.declare(:smtp, 'Email: SMTP configuration', allowed: Hash)
|
|
137
|
-
options.declare(:notify_to, 'Email: Recipient for notification of transfers')
|
|
138
|
-
options.declare(:notify_template, 'Email: ERB template for notification of transfers')
|
|
139
|
-
# HTTP options — declared by HttpConfig itself
|
|
140
|
-
context.http_config.declare_options(options)
|
|
141
|
-
options.declare(:cache_tokens, 'Save and reuse OAuth tokens', allowed: Allowed::TYPES_BOOLEAN, handler: {o: self, m: :option_cache_tokens})
|
|
142
|
-
options.declare(:fpac, 'Proxy auto configuration script')
|
|
143
|
-
options.declare(:proxy_credentials, 'HTTP proxy credentials for fpac: user, password', allowed: [Array, NilClass])
|
|
108
|
+
# HTTP options: declare metadata (class method), then bind to the instance
|
|
109
|
+
Http.declare_options(options)
|
|
110
|
+
context.http_config.bind_options(options)
|
|
144
111
|
options.parse_options!
|
|
145
|
-
@progress_bar = TransferProgress.new if options.get_option(:progress_bar)
|
|
146
|
-
setup_pac_executor
|
|
147
|
-
setup_rest_and_transfer_runtime
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
private
|
|
151
|
-
|
|
152
|
-
def setup_persistency_and_plugin_folders
|
|
153
|
-
context.persistency = PersistencyFolder.new(File.join(@main_folder, PERSISTENCY_FOLDER))
|
|
154
|
-
Plugins::Factory.instance.add_lookup_folder(self.class.gem_plugins_folder)
|
|
155
|
-
Plugins::Factory.instance.add_lookup_folder(File.join(@main_folder, ASPERA_PLUGINS_FOLDERNAME))
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
def setup_extended_value_handlers
|
|
159
|
-
ExtendedValue.instance.on(EXTEND_PRESET){ |v| presets.by_name(v)}
|
|
160
|
-
ExtendedValue.instance.on(EXTEND_VAULT){ |v| vault_value(v)}
|
|
161
|
-
ExtendedValue.instance.on(EXTEND_ARGS){ |v| options.args_as_extended(v)}
|
|
162
|
-
add_plugin_default_preset(CONF_GLOBAL_SYM)
|
|
163
112
|
end
|
|
164
113
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
114
|
+
# DSL command declarations - replaces ACTIONS + execute_action
|
|
115
|
+
command :preset, description: 'Manage configuration presets'
|
|
116
|
+
commands_under :preset do
|
|
117
|
+
command :list, description: 'List all presets'
|
|
118
|
+
command :overview, description: 'Display all options from all presets'
|
|
119
|
+
command :lookup, description: 'Find preset matching URL and username'
|
|
120
|
+
command :secure, description: 'Move secrets to vault',
|
|
121
|
+
arguments: [{name: :config_name, type: String, mandatory: false}]
|
|
122
|
+
command :show, description: 'Display a preset',
|
|
123
|
+
arguments: [{name: :name, type: :identifier}]
|
|
124
|
+
command :delete, description: 'Delete a preset',
|
|
125
|
+
arguments: [{name: :name, type: :identifier}]
|
|
126
|
+
command :get, description: 'Get a single parameter from a preset',
|
|
127
|
+
arguments: [{name: :name, type: :identifier}, {name: :param_name, type: String}]
|
|
128
|
+
command :unset, description: 'Remove a parameter from a preset',
|
|
129
|
+
arguments: [{name: :name, type: :identifier}, {name: :param_name, type: String}]
|
|
130
|
+
command :set, description: 'Set a parameter in a preset',
|
|
131
|
+
arguments: [{name: :name, type: :identifier}, {name: :param_name, type: String},
|
|
132
|
+
{name: :param_value, type: nil}]
|
|
133
|
+
command :initialize, description: 'Initialize a preset with a value',
|
|
134
|
+
arguments: [{name: :name, type: :identifier}, {name: :config_value, type: Hash}]
|
|
135
|
+
command :update, description: 'Update a preset with current option values',
|
|
136
|
+
arguments: [{name: :name, type: :identifier}]
|
|
137
|
+
command :ask, description: 'Interactively ask for option values',
|
|
138
|
+
arguments: [{name: :name, type: :identifier},
|
|
139
|
+
{name: :option_names, type: String, multiple: true, interactive: true}]
|
|
140
|
+
end
|
|
141
|
+
command(
|
|
142
|
+
:open, description: 'Open the configuration file in the default editor',
|
|
143
|
+
action: lambda do
|
|
144
|
+
Environment.instance.open_editor(context.presets.config_file.to_s)
|
|
145
|
+
Result::Nothing.new
|
|
175
146
|
end
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
147
|
+
)
|
|
148
|
+
command :documentation, description: 'Open the documentation in the default browser',
|
|
149
|
+
arguments: [
|
|
150
|
+
{name: :location, type: Symbol, mandatory: false, default: :github, allowed: %i[github local toc]},
|
|
151
|
+
{name: :section, type: String, mandatory: false}
|
|
152
|
+
]
|
|
153
|
+
command :genkey, description: 'Generate a new RSA private key',
|
|
154
|
+
arguments: [
|
|
155
|
+
{name: :private_key_path, type: String},
|
|
156
|
+
{name: :private_key_length, type: Integer, mandatory: false, default: OAuth::Jwt::DEFAULT_PRIV_KEY_LENGTH}
|
|
157
|
+
]
|
|
158
|
+
command :pubkey, description: 'Display the public key of an RSA private key',
|
|
159
|
+
arguments: [{name: :private_key_pem, type: String}],
|
|
160
|
+
action: ->(private_key_pem:, **) { Result::Text.new(OpenSSL::PKey::RSA.new(private_key_pem).public_key.to_s) }
|
|
161
|
+
command :remote_certificate, description: 'Retrieve the certificate chain of a remote HTTPS server'
|
|
162
|
+
command :echo, description: 'Display the value of a given argument',
|
|
163
|
+
arguments: [{name: :value, type: nil}],
|
|
164
|
+
action: ->(value:, **) { Result.auto(value) }
|
|
165
|
+
command :download, description: 'Download a file from a URL',
|
|
166
|
+
arguments: [
|
|
167
|
+
{name: :file_url, type: String},
|
|
168
|
+
{name: :file_dest, type: String, mandatory: false}
|
|
169
|
+
]
|
|
170
|
+
command :tokens, description: 'Manage OAuth tokens'
|
|
171
|
+
command :plugins, description: 'Manage CLI plugins'
|
|
172
|
+
command :detect, description: 'Detect the Aspera product from a URL (interactive)',
|
|
173
|
+
arguments: [{name: :url, type: String}, {name: :plugin_name, mandatory: false, default: nil}]
|
|
174
|
+
command :wizard, description: 'Run the setup wizard for an Aspera product (interactive)',
|
|
175
|
+
arguments: [{name: :url, type: String}, {name: :plugin_name, mandatory: false, default: nil},
|
|
176
|
+
{name: :preset_name, mandatory: false, default: ''}]
|
|
177
|
+
command :coffee, description: 'Display a coffee image', action: -> { Result::Image.new(COFFEE_IMAGE_URL) }
|
|
178
|
+
command :image, description: 'Display an image',
|
|
179
|
+
arguments: [{name: :image_uri, type: nil}],
|
|
180
|
+
action: ->(image_uri:, **) { Result::Image.new(image_uri) }
|
|
181
|
+
command :sync, description: 'Manage Aspera Sync operations'
|
|
182
|
+
command :gem, description: 'Display gem information'
|
|
183
|
+
command :folder, description: 'Display the configuration folder path', action: -> { Result::Text.new(context.main_folder) }
|
|
184
|
+
command :file, description: 'Display the configuration file path', action: -> { Result::Text.new(context.presets.config_file) }
|
|
185
|
+
command(
|
|
186
|
+
:email_test, description: 'Send a test email',
|
|
187
|
+
action: lambda do
|
|
188
|
+
context.mailer.send_email_template(email_template_default: EMAIL_TEST_TEMPLATE)
|
|
189
|
+
Result::Nothing.new
|
|
198
190
|
end
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
public
|
|
209
|
-
|
|
210
|
-
attr_accessor :main_folder, :option_cache_tokens
|
|
211
|
-
attr_reader :progress_bar
|
|
212
|
-
|
|
213
|
-
# Delegations to http_config kept for backward compatibility with transfer_agent and plugins
|
|
214
|
-
def option_insecure; context.http_config.insecure; end
|
|
215
|
-
def option_insecure=(v); context.http_config.insecure = v; end
|
|
216
|
-
def option_warn_insecure_cert; context.http_config.warn_insecure; end
|
|
217
|
-
def option_warn_insecure_cert=(v); context.http_config.warn_insecure = v; end
|
|
218
|
-
def option_http_options; context.http_config.http_options; end
|
|
219
|
-
def option_http_options=(v); context.http_config.http_options = v; end
|
|
220
|
-
def option_ignore_cert_host_port; context.http_config.ignore_cert_host_port; end
|
|
221
|
-
def ignore_cert?(address, port); context.http_config.ignore_cert?(address, port); end
|
|
222
|
-
def trusted_cert_locations; context.http_config.trusted_cert_locations; end
|
|
223
|
-
def trusted_cert_locations=(v); context.http_config.trusted_cert_locations = v; end
|
|
224
|
-
|
|
225
|
-
def set_sdk_dir
|
|
226
|
-
# Check SDK folder is set or not, for compatibility, we check in two places
|
|
227
|
-
sdk_dir = Products::Transferd.sdk_directory rescue nil
|
|
228
|
-
if sdk_dir.nil?
|
|
229
|
-
@sdk_default_location = true
|
|
230
|
-
Log.log.debug('SDK folder is not set, checking default')
|
|
231
|
-
# New location
|
|
232
|
-
sdk_dir = self.class.default_app_main_folder(app_name: TRANSFERD_APP_NAME)
|
|
233
|
-
Log.log.debug{"Checking: #{sdk_dir}"}
|
|
234
|
-
if !Dir.exist?(sdk_dir)
|
|
235
|
-
Log.log.debug{"No such folder: #{sdk_dir}"}
|
|
236
|
-
# Former location
|
|
237
|
-
former_sdk_folder = File.join(self.class.default_app_main_folder(app_name: Info::CMD_NAME), TRANSFERD_APP_NAME)
|
|
238
|
-
Log.log.debug{"Checking: #{former_sdk_folder}"}
|
|
239
|
-
sdk_dir = former_sdk_folder if Dir.exist?(former_sdk_folder)
|
|
240
|
-
end
|
|
241
|
-
Log.log.debug{"Using: #{sdk_dir}"}
|
|
242
|
-
Products::Transferd.sdk_directory = sdk_dir
|
|
191
|
+
)
|
|
192
|
+
command :smtp_settings, description: 'Display the current SMTP settings', action: -> { Result::SingleObject.new(context.mailer.email_settings) }
|
|
193
|
+
command(
|
|
194
|
+
:proxy_check, description: 'Check the proxy returned by the PAC script for a given URL',
|
|
195
|
+
arguments: [{name: :server_url, type: String}],
|
|
196
|
+
action: lambda do |server_url:, **|
|
|
197
|
+
Aspera.assert(!context.pac_executor.nil?, type: Cli::BadArgument) { 'No PAC script configured, use --fpac' }
|
|
198
|
+
Result::ValueList.new(context.pac_executor.get_proxies(server_url), name: 'proxy')
|
|
243
199
|
end
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
200
|
+
)
|
|
201
|
+
command :check_update, description: 'Check if a newer version of the gem is available', action: -> { Result::SingleObject.new(check_gem_version) }
|
|
202
|
+
command :initdemo, description: 'Initialize the demo server preset'
|
|
203
|
+
command :vault, description: 'Manage secrets in the vault'
|
|
204
|
+
commands_under :vault do
|
|
205
|
+
command :info, description: 'Display vault information',
|
|
206
|
+
action: ->(**) { Result::SingleObject.new(vault_required.info) }
|
|
207
|
+
command :ids, description: 'List secret labels in the vault',
|
|
208
|
+
action: ->(**) { Result::ObjectList.new(vault_required.ids) }
|
|
209
|
+
command :list, description: 'List all secrets with full details',
|
|
210
|
+
action: ->(**) { Result::ObjectList.new(vault_required.all) }
|
|
211
|
+
command :show, description: 'Show a secret by label (or id)',
|
|
212
|
+
arguments: [{name: :label, type: String}, {name: :id, type: String, mandatory: false, default: nil}]
|
|
213
|
+
command :create, description: 'Add a new secret to the vault',
|
|
214
|
+
arguments: [{name: :info, type: Hash, schema: Schema::Registry::VAULT_SECRET}]
|
|
215
|
+
command :delete, description: 'Delete a secret by label (or id)',
|
|
216
|
+
arguments: [{name: :label, type: String}, {name: :id, type: String, mandatory: false, default: nil}]
|
|
217
|
+
command :password, description: 'Change the vault password',
|
|
218
|
+
arguments: [{name: :new_password, type: String}]
|
|
219
|
+
command :import, description: 'Import secrets from a JSON array (supports --bulk)',
|
|
220
|
+
arguments: [{name: :secrets, type: Array, schema: {type: 'array', items: {'$ref' => Schema::Registry::VAULT_SECRET}}}]
|
|
221
|
+
end
|
|
222
|
+
command :commands, description: 'List all available commands across all plugins'
|
|
223
|
+
command :options, description: 'List all options available for a plugin',
|
|
224
|
+
arguments: [{name: :plugin_name, type: String}]
|
|
225
|
+
command :test, description: 'Internal test commands'
|
|
226
|
+
command :platform, description: 'Display the current platform/architecture', action: -> { Result::Text.new(Environment.instance.architecture) }
|
|
227
|
+
command :completion, description: 'Generate shell completion scripts'
|
|
228
|
+
|
|
229
|
+
# remote_certificate sub-commands
|
|
230
|
+
commands_under :remote_certificate do
|
|
231
|
+
command :chain, description: 'Display the full certificate chain as PEM',
|
|
232
|
+
arguments: [{name: :remote_url, type: String}]
|
|
233
|
+
command :only, description: 'Display only the server certificate as PEM',
|
|
234
|
+
arguments: [{name: :remote_url, type: String}]
|
|
235
|
+
command :name, description: 'Display the CN of the server certificate',
|
|
236
|
+
arguments: [{name: :remote_url, type: String}]
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
# tokens sub-commands
|
|
240
|
+
commands_under :tokens do
|
|
241
|
+
command(
|
|
242
|
+
:flush, description: 'Delete all cached OAuth tokens',
|
|
243
|
+
action: lambda do
|
|
244
|
+
require 'aspera/api/node'
|
|
245
|
+
Result::ValueList.new(OAuth::Factory.instance.flush_tokens, name: 'file')
|
|
254
246
|
end
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
247
|
+
)
|
|
248
|
+
command(
|
|
249
|
+
:list, description: 'List all cached OAuth tokens',
|
|
250
|
+
action: lambda do
|
|
251
|
+
require 'aspera/api/node'
|
|
252
|
+
Result::ObjectList.new(OAuth::Factory.instance.persisted_tokens)
|
|
259
253
|
end
|
|
260
|
-
end
|
|
261
|
-
return {
|
|
262
|
-
name: Info::GEM_NAME,
|
|
263
|
-
current: Cli::VERSION,
|
|
264
|
-
latest: latest_version,
|
|
265
|
-
need_update: Gem::Version.new(Cli::VERSION) < Gem::Version.new(latest_version)
|
|
266
|
-
}
|
|
267
|
-
end
|
|
268
|
-
|
|
269
|
-
def periodic_check_newer_gem_version
|
|
270
|
-
# Get verification period
|
|
271
|
-
delay_days = options.get_option(:version_check_days, mandatory: true).to_i
|
|
272
|
-
# Check only if not zero day
|
|
273
|
-
return if delay_days.eql?(0)
|
|
274
|
-
# Get last date from persistency
|
|
275
|
-
last_check_array = []
|
|
276
|
-
check_date_persist = PersistencyActionOnce.new(
|
|
277
|
-
manager: persistency,
|
|
278
|
-
data: last_check_array,
|
|
279
|
-
id: 'version_last_check'
|
|
280
254
|
)
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
255
|
+
command :show, description: 'Show details of a cached OAuth token',
|
|
256
|
+
arguments: [{name: :token_id, type: :identifier}]
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
# plugins sub-commands
|
|
260
|
+
commands_under :plugins do
|
|
261
|
+
command :list, description: 'List all available plugins'
|
|
262
|
+
command :create, description: 'Create a new plugin skeleton file',
|
|
263
|
+
arguments: [
|
|
264
|
+
{name: :name, type: String},
|
|
265
|
+
{name: :folder, type: String, mandatory: false}
|
|
266
|
+
]
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
# ascp sub-commands
|
|
270
|
+
command :ascp, description: 'Manage FASP/ascp transfer engine'
|
|
271
|
+
commands_under :ascp do
|
|
272
|
+
command :show, description: 'Display ascp binary path'
|
|
273
|
+
command :info, description: 'Display ascp and transfer spec information'
|
|
274
|
+
command :install, description: 'Install the transfer SDK',
|
|
275
|
+
arguments: [{name: :version, mandatory: false, default: nil}]
|
|
276
|
+
command :spec, description: 'Display the transfer spec schema'
|
|
277
|
+
command :schema, description: 'Display the transfer spec JSON schema',
|
|
278
|
+
arguments: [{name: :agent_name, mandatory: false, default: nil}]
|
|
279
|
+
command :errors, description: 'Display FASP error codes'
|
|
280
|
+
command :products, description: 'Manage installed Aspera products'
|
|
281
|
+
commands_under :products do
|
|
282
|
+
command :list, description: 'List installed Aspera products'
|
|
283
|
+
end
|
|
294
284
|
end
|
|
295
285
|
|
|
296
|
-
#
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
286
|
+
# agents sub-commands
|
|
287
|
+
command :agents, description: 'Manage transfer agents'
|
|
288
|
+
commands_under :agents do
|
|
289
|
+
command :list, description: 'List all transfer agents'
|
|
290
|
+
command :show, description: 'Show details for a transfer agent',
|
|
291
|
+
arguments: [{name: :agent_name, allowed: Agent::Factory::ALL.keys}]
|
|
292
|
+
command :parameters, description: 'Show configurable parameters for a transfer agent',
|
|
293
|
+
arguments: [{name: :agent_name, allowed: Agent::Factory::ALL.keys}]
|
|
301
294
|
end
|
|
302
295
|
|
|
303
|
-
#
|
|
304
|
-
|
|
305
|
-
|
|
296
|
+
# transfer async management sub-commands
|
|
297
|
+
command :transfer, description: 'Manage asynchronous transfers'
|
|
298
|
+
commands_under :transfer do
|
|
299
|
+
command :list, description: 'List all async transfer jobs'
|
|
300
|
+
command :status, description: 'Show status of an async transfer job',
|
|
301
|
+
arguments: [{name: :job_id, type: String}]
|
|
302
|
+
command :cleanup, description: 'Remove completed/failed/cancelled transfer entries'
|
|
306
303
|
end
|
|
307
304
|
|
|
308
|
-
|
|
309
|
-
|
|
305
|
+
# transferd sub-commands
|
|
306
|
+
command :transferd, description: 'Manage the transfer daemon (transferd)'
|
|
307
|
+
commands_under :transferd do
|
|
308
|
+
command :install, description: 'Install the transfer daemon'
|
|
309
|
+
command :list, description: 'List available SDK locations'
|
|
310
310
|
end
|
|
311
311
|
|
|
312
|
-
|
|
313
|
-
|
|
312
|
+
# sync sub-commands
|
|
313
|
+
commands_under :sync do
|
|
314
|
+
command(
|
|
315
|
+
:spec, description: 'Display the sync configuration schema',
|
|
316
|
+
action: lambda do
|
|
317
|
+
builder = Schema::Documentation.new(TerminalFormatter, Sync::Operations::CONF_SCHEMA, include_option: true).build
|
|
318
|
+
Result::ObjectList.new(builder.rows, fields: builder.columns)
|
|
319
|
+
end
|
|
320
|
+
)
|
|
321
|
+
command :admin, description: 'Manage sync database (admin operations)'
|
|
322
|
+
SyncActions.register_sync_admin_commands(self, :admin)
|
|
323
|
+
command :translate, description: 'Translate async-style arguments to sync config format',
|
|
324
|
+
arguments: [{name: :async_arguments, type: String, multiple: true}],
|
|
325
|
+
action: ->(async_arguments:, **) { Result::SingleObject.new(Sync::Operations.args_to_conf(async_arguments)) }
|
|
314
326
|
end
|
|
315
327
|
|
|
316
|
-
|
|
317
|
-
|
|
328
|
+
# gem sub-commands
|
|
329
|
+
commands_under :gem do
|
|
330
|
+
command :path, description: 'Display the gem source root path', action: -> { Result::Text.new(self.class.gem_src_root) }
|
|
331
|
+
command :version, description: 'Display the gem version', action: -> { Result::Text.new(Cli::VERSION) }
|
|
332
|
+
command :name, description: 'Display the gem name', action: -> { Result::Text.new(Info::GEM_NAME) }
|
|
318
333
|
end
|
|
319
334
|
|
|
320
|
-
|
|
321
|
-
|
|
335
|
+
# test sub-commands
|
|
336
|
+
commands_under :test do
|
|
337
|
+
command :throw, description: 'Raise an exception (for testing)',
|
|
338
|
+
arguments: [
|
|
339
|
+
{name: :exception_class_name, type: String},
|
|
340
|
+
{name: :exception_text, type: String}
|
|
341
|
+
]
|
|
342
|
+
command :web, description: 'Test web browser interaction', action: -> {}
|
|
322
343
|
end
|
|
323
344
|
|
|
324
|
-
|
|
325
|
-
|
|
345
|
+
# completion sub-commands
|
|
346
|
+
commands_under :completion do
|
|
347
|
+
command :bash, description: 'Generate bash completion script',
|
|
348
|
+
arguments: [{name: :words, type: String, multiple: true, mandatory: false}]
|
|
326
349
|
end
|
|
327
350
|
|
|
328
|
-
|
|
329
|
-
presets.lookup_preset(url: url, username: username)
|
|
330
|
-
end
|
|
351
|
+
attr_accessor :option_cache_tokens
|
|
331
352
|
|
|
332
353
|
attr_reader :gem_url
|
|
333
|
-
attr_accessor :option_config_file
|
|
334
354
|
|
|
335
355
|
def option_plugin_folder=(value)
|
|
336
356
|
value = [value] unless value.is_a?(Array)
|
|
337
|
-
Aspera.assert_array_all(value, String){'plugin folder(s)'}
|
|
338
|
-
value.each{ |f| Plugins::Factory.instance.add_lookup_folder(f)}
|
|
357
|
+
Aspera.assert_array_all(value, String) { 'plugin folder(s)' }
|
|
358
|
+
value.each { |f| Plugins::Factory.instance.add_lookup_folder(f) }
|
|
339
359
|
end
|
|
340
360
|
|
|
341
361
|
def option_plugin_folder
|
|
@@ -349,554 +369,223 @@ module Aspera
|
|
|
349
369
|
when Hash
|
|
350
370
|
options.add_option_preset(value, 'set')
|
|
351
371
|
when String
|
|
352
|
-
options.add_option_preset(
|
|
372
|
+
options.add_option_preset(presets.by_name(value), 'set_by_name')
|
|
353
373
|
else
|
|
354
374
|
raise BadArgument, 'Preset definition must be a String for preset name, or Hash for set of values'
|
|
355
375
|
end
|
|
356
376
|
end
|
|
357
377
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
# Add command line transfer spec
|
|
375
|
-
data['ts'] = transfer.user_transfer_spec
|
|
376
|
-
# Add keys
|
|
377
|
-
DataRepository::ELEMENTS.each_with_object(data){ |i, h| h[i.to_s] = DataRepository.instance.item(i)}
|
|
378
|
-
# Declare those as secrets
|
|
379
|
-
SecretHider::ADDITIONAL_KEYS_TO_HIDE.concat(DataRepository::ELEMENTS.map(&:to_s))
|
|
380
|
-
return Result::SingleObject.new(data)
|
|
381
|
-
when :products
|
|
382
|
-
command = options.get_next_command(%i[list])
|
|
383
|
-
case command
|
|
384
|
-
when :list
|
|
385
|
-
return Result::ObjectList.new(Ascp::Installation.instance.installed_products, fields: %w[name app_root])
|
|
378
|
+
# DSL handlers - one method per leaf command
|
|
379
|
+
|
|
380
|
+
def action_documentation(location: :github, section: nil, **)
|
|
381
|
+
case location
|
|
382
|
+
when :github
|
|
383
|
+
section = "##{section}" unless section.nil?
|
|
384
|
+
Environment.instance.open_uri("#{Info::DOC_URL}#{section}")
|
|
385
|
+
return Result::Nothing.new
|
|
386
|
+
when :toc, :local
|
|
387
|
+
require 'aspera/markdown'
|
|
388
|
+
local_doc = File.join(self.class.gem_src_root, '..', 'docs', 'README.md')
|
|
389
|
+
Aspera.assert(File.exist?(local_doc), type: Cli::Error) { "Local documentation not found: #{local_doc}" }
|
|
390
|
+
content = File.read(local_doc)
|
|
391
|
+
if location == :toc
|
|
392
|
+
entries = Markdown.toc(content)
|
|
393
|
+
return Result::ObjectList.new(entries, fields: %w[level title anchor])
|
|
386
394
|
end
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
when :schema
|
|
393
|
-
schema = Transfer::Spec::SCHEMA.current.merge({'$comment'=>'DO NOT EDIT, this file was generated from the YAML.'})
|
|
394
|
-
agent = options.get_next_argument('transfer agent name', mandatory: false)
|
|
395
|
-
schema['properties'] = schema['properties'].select{ |_k, v| CommandLineBuilder.supported_by_agent(agent, v)} unless agent.nil?
|
|
396
|
-
schema['properties'] = schema['properties'].sort.to_h
|
|
397
|
-
return Result::SingleObject.new(schema)
|
|
398
|
-
when :errors
|
|
399
|
-
error_data = []
|
|
400
|
-
Ascp::Management::ERRORS.each_pair do |code, prop|
|
|
401
|
-
error_data.push(code: code, mnemonic: prop[:c], retry: prop[:r], info: prop[:a])
|
|
395
|
+
# :local
|
|
396
|
+
if section
|
|
397
|
+
text = Markdown.extract_section(content, section)
|
|
398
|
+
Aspera.assert(!text.nil?, type: Cli::Error) { "Section not found: #{section}" }
|
|
399
|
+
return Result::Text.new(text)
|
|
402
400
|
end
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
def execute_action_transferd
|
|
410
|
-
command = options.get_next_command(%i[list install])
|
|
411
|
-
case command
|
|
412
|
-
when :install
|
|
413
|
-
return install_transfer_sdk
|
|
414
|
-
when :list
|
|
415
|
-
sdk_list = Ascp::Installation.instance.sdk_locations
|
|
416
|
-
return Result::ObjectList.new(
|
|
417
|
-
sdk_list,
|
|
418
|
-
fields: sdk_list.first.keys - ['url']
|
|
419
|
-
)
|
|
420
|
-
else Aspera.error_unexpected_value(command)
|
|
401
|
+
if Environment.instance.url_method.eql?(:graphical)
|
|
402
|
+
Environment.instance.open_uri("file://#{local_doc}")
|
|
403
|
+
return Result::Nothing.new
|
|
404
|
+
end
|
|
405
|
+
return Result::Text.new(content)
|
|
421
406
|
end
|
|
422
|
-
Aspera.error_unreachable_line
|
|
423
407
|
end
|
|
424
408
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
# require id
|
|
430
|
-
PRESET_INSTANCE_ACTIONS = %i[initialize update ask set].concat(PRESET_EXIST_ACTIONS).freeze
|
|
431
|
-
PRESET_ALL_ACTIONS = (PRESET_GBL_ACTIONS + PRESET_INSTANCE_ACTIONS).freeze
|
|
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
|
|
432
413
|
|
|
433
|
-
def
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
name = presets.global_default_preset if name.eql?(GLOBAL_DEFAULT_KEYWORD)
|
|
438
|
-
# Those operations require existing option
|
|
439
|
-
raise "no such preset: #{name}" if PRESET_EXIST_ACTIONS.include?(action) && !cp.key?(name)
|
|
440
|
-
case action
|
|
441
|
-
when :list
|
|
442
|
-
return Result::ValueList.new(cp.keys, name: 'name')
|
|
443
|
-
when :overview
|
|
444
|
-
# Display process modifies the value (hide secrets): we do not want to save removed secrets
|
|
445
|
-
data = PresetManager.deep_clone(cp)
|
|
446
|
-
formatter.hide_secrets(data)
|
|
447
|
-
result = []
|
|
448
|
-
data.each do |config, preset|
|
|
449
|
-
preset.each do |parameter, value|
|
|
450
|
-
result.push(CONF_OVERVIEW_KEYS.zip([config, parameter, value]).to_h)
|
|
451
|
-
end
|
|
452
|
-
end
|
|
453
|
-
return Result::ObjectList.new(result, fields: CONF_OVERVIEW_KEYS)
|
|
454
|
-
when :show
|
|
455
|
-
return Result::SingleObject.new(PresetManager.deep_clone(cp[name]))
|
|
456
|
-
when :delete
|
|
457
|
-
cp.delete(name)
|
|
458
|
-
return Result::Status.new("Deleted: #{name}")
|
|
459
|
-
when :get
|
|
460
|
-
param_name = options.get_next_argument('parameter name')
|
|
461
|
-
value = cp[name][param_name]
|
|
462
|
-
raise "no such option in preset #{name} : #{param_name}" if value.nil?
|
|
463
|
-
case value
|
|
464
|
-
when Numeric, String then return Result::Text.new(ExtendedValue.instance.evaluate(value.to_s, context: 'preset'))
|
|
465
|
-
end
|
|
466
|
-
return Result::SingleObject.new(value)
|
|
467
|
-
when :unset
|
|
468
|
-
param_name = options.get_next_argument('parameter name')
|
|
469
|
-
cp[name].delete(param_name)
|
|
470
|
-
return Result::Status.new("Removed: #{name}: #{param_name}")
|
|
471
|
-
when :set
|
|
472
|
-
param_name = options.get_next_argument('parameter name')
|
|
473
|
-
param_name = Manager.option_line_to_name(param_name)
|
|
474
|
-
param_value = options.get_next_argument('parameter value', validation: nil)
|
|
475
|
-
set_preset_key(name, param_name, param_value)
|
|
476
|
-
return Result::Nothing.new
|
|
477
|
-
when :initialize
|
|
478
|
-
config_value = options.get_next_argument('extended value', validation: Hash)
|
|
479
|
-
Log.log.warn{"configuration already exists: #{name}, overwriting"} if cp.key?(name)
|
|
480
|
-
cp[name] = config_value
|
|
481
|
-
return Result::Status.new("Modified: #{@option_config_file}")
|
|
482
|
-
when :update
|
|
483
|
-
unprocessed_options = options.unprocessed_options_with_value
|
|
484
|
-
Log.log.debug{"opts=#{unprocessed_options}"}
|
|
485
|
-
cp[name] ||= {}
|
|
486
|
-
cp[name].merge!(unprocessed_options)
|
|
487
|
-
return Result::Status.new("Updated: #{name}")
|
|
488
|
-
when :ask
|
|
489
|
-
options.ask_missing_mandatory = true
|
|
490
|
-
cp[name] ||= {}
|
|
491
|
-
options.get_next_argument('option names', multiple: true).each do |option_name|
|
|
492
|
-
option_value = options.get_interactive(option_name, check_option: true)
|
|
493
|
-
cp[name][option_name] = option_value
|
|
494
|
-
end
|
|
495
|
-
return Result::Status.new("Updated: #{name}")
|
|
496
|
-
when :lookup
|
|
497
|
-
BasicAuth.declare_options(options)
|
|
498
|
-
url = options.get_option(:url, mandatory: true)
|
|
499
|
-
user = options.get_option(:username, mandatory: true)
|
|
500
|
-
result = lookup_preset(url: url, username: user)
|
|
501
|
-
raise Error, 'no such config found' if result.nil?
|
|
502
|
-
return Result::SingleObject.new(result)
|
|
503
|
-
when :secure
|
|
504
|
-
identifier = options.get_next_argument('config name', mandatory: false)
|
|
505
|
-
preset_names = identifier.nil? ? cp.keys : [identifier]
|
|
506
|
-
secret_keywords = %w[password secret].freeze
|
|
507
|
-
preset_names.each do |preset_name|
|
|
508
|
-
preset = cp[preset_name]
|
|
509
|
-
next unless preset.is_a?(Hash)
|
|
510
|
-
preset.each_key do |option_name|
|
|
511
|
-
secret_keywords.each do |keyword|
|
|
512
|
-
next unless option_name.end_with?(keyword)
|
|
513
|
-
vault_label = preset_name
|
|
514
|
-
incr = 0
|
|
515
|
-
until vault.get(label: vault_label, exception: false).nil?
|
|
516
|
-
vault_label = "#{preset_name}#{incr}"
|
|
517
|
-
incr += 1
|
|
518
|
-
end
|
|
519
|
-
to_set = {label: vault_label, password: preset[option_name]}
|
|
520
|
-
puts "need to encode #{preset_name}.#{option_name} -> #{vault_label} -> #{to_set}"
|
|
521
|
-
vault.set(to_set)
|
|
522
|
-
preset[option_name] = "@vault:#{vault_label}.password"
|
|
523
|
-
end
|
|
524
|
-
end
|
|
525
|
-
end
|
|
526
|
-
return Result::Status.new('Secrets secured in vault: Make sure to save the vault password securely.')
|
|
527
|
-
end
|
|
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"))
|
|
528
418
|
end
|
|
529
419
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
return Result::Text.new(OpenSSL::PKey::RSA.new(private_key_pem).public_key.to_s)
|
|
584
|
-
when :remote_certificate
|
|
585
|
-
cert_action = options.get_next_command(%i[chain only name])
|
|
586
|
-
remote_url = options.get_next_argument('remote URL')
|
|
587
|
-
remote_chain = Rest.remote_certificate_chain(remote_url, as_string: false)
|
|
588
|
-
raise "No certificate found for #{remote_url}" unless remote_chain&.first
|
|
589
|
-
case cert_action
|
|
590
|
-
when :chain
|
|
591
|
-
return Result::Text.new(remote_chain.map(&:to_pem).join("\n"))
|
|
592
|
-
when :only
|
|
593
|
-
return Result::Text.new(remote_chain.first.to_pem)
|
|
594
|
-
when :name
|
|
595
|
-
return Result::Text.new(remote_chain.first.subject.to_a.find{ |name, _, _| name == 'CN'}[1])
|
|
596
|
-
end
|
|
597
|
-
when :echo # Display the content of a value given on command line
|
|
598
|
-
return Result.auto(options.get_next_argument('value', validation: nil))
|
|
599
|
-
when :download
|
|
600
|
-
file_url = options.get_next_argument('source URL').chomp
|
|
601
|
-
file_dest = options.get_next_argument('file path', mandatory: false)
|
|
602
|
-
file_dest = File.join(transfer.destination_folder(Transfer::Spec::DIRECTION_RECEIVE), file_url.gsub(%r{.*/}, '')) if file_dest.nil?
|
|
603
|
-
Log.log.info("Downloading: #{file_url}")
|
|
604
|
-
Rest.new(base_url: file_url).call(operation: 'GET', save_to_file: file_dest)
|
|
605
|
-
return Result::Status.new("Saved to: #{file_dest}")
|
|
606
|
-
when :tokens
|
|
607
|
-
require 'aspera/api/node'
|
|
608
|
-
case options.get_next_command(%i{flush list show})
|
|
609
|
-
when :flush
|
|
610
|
-
return Result::ValueList.new(OAuth::Factory.instance.flush_tokens, name: 'file')
|
|
611
|
-
when :list
|
|
612
|
-
return Result::ObjectList.new(OAuth::Factory.instance.persisted_tokens)
|
|
613
|
-
when :show
|
|
614
|
-
data = OAuth::Factory.instance.get_token_info(options.instance_identifier)
|
|
615
|
-
raise Cli::Error, 'Unknown identifier' if data.nil?
|
|
616
|
-
return Result::SingleObject.new(data)
|
|
617
|
-
end
|
|
618
|
-
when :plugins
|
|
619
|
-
case options.get_next_command(%i[list create])
|
|
620
|
-
when :list
|
|
621
|
-
result = []
|
|
622
|
-
Plugins::Factory.instance.plugin_list.each do |name|
|
|
623
|
-
plugin_class = Plugins::Factory.instance.plugin_class(name)
|
|
624
|
-
result.push({
|
|
625
|
-
plugin: name,
|
|
626
|
-
detect: TerminalFormatter.tick(plugin_class.respond_to?(:detect)),
|
|
627
|
-
wizard: TerminalFormatter.tick(plugin_class.method_defined?(:wizard)),
|
|
628
|
-
path: Plugins::Factory.instance.plugin_source(name)
|
|
629
|
-
})
|
|
630
|
-
end
|
|
631
|
-
return Result::ObjectList.new(result, fields: %w[plugin detect wizard path])
|
|
632
|
-
when :create
|
|
633
|
-
plugin_name = options.get_next_argument('name').downcase
|
|
634
|
-
destination_folder = options.get_next_argument('folder', mandatory: false) || File.join(@main_folder, ASPERA_PLUGINS_FOLDERNAME)
|
|
635
|
-
plugin_file = File.join(destination_folder, "#{plugin_name}.rb")
|
|
636
|
-
content = <<~END_OF_PLUGIN_CODE
|
|
637
|
-
require 'aspera/cli/plugins/base'
|
|
638
|
-
module Aspera
|
|
639
|
-
module Cli
|
|
640
|
-
module Plugins
|
|
641
|
-
class #{plugin_name.snake_to_capital} < Base
|
|
642
|
-
ACTIONS=[]
|
|
643
|
-
def execute_action
|
|
644
|
-
return Result::Status.new('You called plugin #{plugin_name}')
|
|
645
|
-
end
|
|
646
|
-
end
|
|
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
|
+
def action_download(file_url:, file_dest: nil, **)
|
|
433
|
+
file_url = file_url.chomp
|
|
434
|
+
file_dest = File.join(transfer.destination_folder(Transfer::Spec::DIRECTION_RECEIVE), file_url.gsub(%r{.*/}, '')) if file_dest.nil?
|
|
435
|
+
Log.log.info("Downloading: #{file_url}")
|
|
436
|
+
Rest.new(base_url: file_url).call(operation: 'GET', save_to: file_dest)
|
|
437
|
+
Result::Status.new("Saved to: #{file_dest}")
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
def action_tokens_show(token_id:, **)
|
|
441
|
+
require 'aspera/api/node'
|
|
442
|
+
data = OAuth::Factory.instance.get_token_info(token_id)
|
|
443
|
+
Aspera.assert(!data.nil?, type: Cli::Error) { 'Unknown identifier' }
|
|
444
|
+
Result::SingleObject.new(data)
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
def action_plugins_list
|
|
448
|
+
result = Plugins::Factory.instance.plugin_list.map do |name|
|
|
449
|
+
plugin_class = Plugins::Factory.instance.plugin_class(name)
|
|
450
|
+
{
|
|
451
|
+
plugin: name,
|
|
452
|
+
detect: TerminalFormatter.tick(plugin_class.respond_to?(:detect)),
|
|
453
|
+
wizard: TerminalFormatter.tick(plugin_class.method_defined?(:wizard)),
|
|
454
|
+
path: Plugins::Factory.instance.plugin_source(name)
|
|
455
|
+
}
|
|
456
|
+
end
|
|
457
|
+
Result::ObjectList.new(result, fields: %w[plugin detect wizard path])
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
def action_plugins_create(name:, folder: nil, **)
|
|
461
|
+
plugin_name = name.downcase
|
|
462
|
+
destination_folder = folder || File.join(context.main_folder, ASPERA_PLUGINS_FOLDERNAME)
|
|
463
|
+
plugin_file = File.join(destination_folder, "#{plugin_name}.rb")
|
|
464
|
+
content = <<~END_OF_PLUGIN_CODE
|
|
465
|
+
require 'aspera/cli/plugins/base'
|
|
466
|
+
module Aspera
|
|
467
|
+
module Cli
|
|
468
|
+
module Plugins
|
|
469
|
+
class #{plugin_name.snake_to_capital} < Base
|
|
470
|
+
command :example, description: 'example command', action: :action_example
|
|
471
|
+
def action_example
|
|
472
|
+
Result::Status.new('You called plugin #{plugin_name}')
|
|
647
473
|
end
|
|
648
474
|
end
|
|
649
475
|
end
|
|
650
|
-
|
|
651
|
-
File.write(plugin_file, content)
|
|
652
|
-
return Result::Status.new("Created #{plugin_file}")
|
|
653
|
-
end
|
|
654
|
-
when :detect, :wizard
|
|
655
|
-
# Interactive mode
|
|
656
|
-
options.ask_missing_mandatory = true
|
|
657
|
-
# Detect plugins by url and optional query
|
|
658
|
-
apps = @wizard.identify_plugins_for_url.freeze
|
|
659
|
-
return Result::ObjectList.new(apps) if action.eql?(:detect)
|
|
660
|
-
return @wizard.find(apps)
|
|
661
|
-
when :coffee
|
|
662
|
-
return Result::Image.new(COFFEE_IMAGE_URL)
|
|
663
|
-
when :image
|
|
664
|
-
return Result::Image.new(options.get_next_argument('image URI or blob'))
|
|
665
|
-
when :ascp
|
|
666
|
-
execute_action_ascp
|
|
667
|
-
when :sync
|
|
668
|
-
SyncActions.declare_options(options)
|
|
669
|
-
case options.get_next_command(%i[spec admin translate])
|
|
670
|
-
when :spec
|
|
671
|
-
builder = Schema::Documentation.new(TerminalFormatter, Sync::Operations::CONF_SCHEMA, include_option: true).build
|
|
672
|
-
return Result::ObjectList.new(builder.rows, fields: builder.columns)
|
|
673
|
-
when :admin
|
|
674
|
-
return execute_sync_admin
|
|
675
|
-
when :translate
|
|
676
|
-
return Result::SingleObject.new(Sync::Operations.args_to_conf(options.get_next_argument('async arguments', multiple: true)))
|
|
677
|
-
else Aspera.error_unreachable_line
|
|
678
|
-
end
|
|
679
|
-
when :transferd
|
|
680
|
-
execute_action_transferd
|
|
681
|
-
when :gem
|
|
682
|
-
case options.get_next_command(%i[path version name])
|
|
683
|
-
when :path then return Result::Text.new(self.class.gem_src_root)
|
|
684
|
-
when :version then return Result::Text.new(Cli::VERSION)
|
|
685
|
-
when :name then return Result::Text.new(Info::GEM_NAME)
|
|
686
|
-
else Aspera.error_unreachable_line
|
|
687
|
-
end
|
|
688
|
-
when :folder
|
|
689
|
-
return Result::Text.new(@main_folder)
|
|
690
|
-
when :file
|
|
691
|
-
return Result::Text.new(@option_config_file)
|
|
692
|
-
when :email_test
|
|
693
|
-
send_email_template(email_template_default: EMAIL_TEST_TEMPLATE)
|
|
694
|
-
return Result::Nothing.new
|
|
695
|
-
when :smtp_settings
|
|
696
|
-
return Result::SingleObject.new(email_settings)
|
|
697
|
-
when :proxy_check
|
|
698
|
-
# Ensure fpac was provided
|
|
699
|
-
options.get_option(:fpac, mandatory: true)
|
|
700
|
-
server_url = options.get_next_argument('server url')
|
|
701
|
-
return Result::ValueList.new(@pac_exec.get_proxies(server_url), name: 'proxy')
|
|
702
|
-
when :check_update
|
|
703
|
-
return Result::SingleObject.new(check_gem_version)
|
|
704
|
-
when :initdemo
|
|
705
|
-
cp = presets.config_presets
|
|
706
|
-
if cp.key?(DEMO_PRESET)
|
|
707
|
-
Log.log.warn{"Demo server preset already present: #{DEMO_PRESET}"}
|
|
708
|
-
else
|
|
709
|
-
Log.log.info{"Creating Demo server preset: #{DEMO_PRESET}"}
|
|
710
|
-
cp[DEMO_PRESET] = {
|
|
711
|
-
'url' => "ssh://#{DEMO_SERVER}.asperasoft.com:33001",
|
|
712
|
-
'username' => ASPERA,
|
|
713
|
-
'ssAP'.downcase.reverse + 'drow'.reverse => DEMO_SERVER + ASPERA # cspell:disable-line
|
|
714
|
-
}
|
|
715
|
-
end
|
|
716
|
-
cp[PresetManager::Key::DEFAULTS] ||= {}
|
|
717
|
-
if cp[PresetManager::Key::DEFAULTS].key?(SERVER_COMMAND)
|
|
718
|
-
Log.log.warn{"Server default preset already set to: #{cp[PresetManager::Key::DEFAULTS][SERVER_COMMAND]}"}
|
|
719
|
-
Log.log.warn{"Use #{DEMO_PRESET} for demo: -P#{DEMO_PRESET}"} unless
|
|
720
|
-
DEMO_PRESET.eql?(cp[PresetManager::Key::DEFAULTS][SERVER_COMMAND])
|
|
721
|
-
else
|
|
722
|
-
cp[PresetManager::Key::DEFAULTS][SERVER_COMMAND] = DEMO_PRESET
|
|
723
|
-
Log.log.info{"Setting server default preset to : #{DEMO_PRESET}"}
|
|
476
|
+
end
|
|
724
477
|
end
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
when :platform
|
|
729
|
-
return Result::Text.new(Environment.instance.architecture)
|
|
730
|
-
when :completion
|
|
731
|
-
return execute_completion
|
|
732
|
-
else Aspera.error_unreachable_line
|
|
733
|
-
end
|
|
478
|
+
END_OF_PLUGIN_CODE
|
|
479
|
+
File.write(plugin_file, content)
|
|
480
|
+
Result::Status.new("Created #{plugin_file}")
|
|
734
481
|
end
|
|
735
482
|
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
end
|
|
747
|
-
Process.exit(0)
|
|
748
|
-
else Aspera.error_unreachable_line
|
|
749
|
-
end
|
|
483
|
+
def action_detect(url:, plugin_name: nil, **)
|
|
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)
|
|
750
493
|
end
|
|
751
494
|
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
smtp = smtp.symbolize_keys
|
|
757
|
-
unsupported = smtp.keys - SMTP_CONF_PARAMS
|
|
758
|
-
raise Cli::Error, "Unsupported SMTP parameter: #{unsupported.join(', ')}, use: #{SMTP_CONF_PARAMS.join(', ')}" unless unsupported.empty?
|
|
759
|
-
# Defaults
|
|
760
|
-
# smtp[:ssl] = nil (false)
|
|
761
|
-
smtp[:tls] = !smtp[:ssl] unless smtp.key?(:tls)
|
|
762
|
-
smtp[:port] ||= if smtp[:tls]
|
|
763
|
-
587
|
|
764
|
-
elsif smtp[:ssl]
|
|
765
|
-
465
|
|
495
|
+
def action_initdemo
|
|
496
|
+
cp = presets.config_presets
|
|
497
|
+
if cp.key?(DEMO_PRESET)
|
|
498
|
+
Log.log.warn { "Demo server preset already present: #{DEMO_PRESET}" }
|
|
766
499
|
else
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
%i[server port domain].each do |n|
|
|
774
|
-
Aspera.assert(smtp.key?(n)){"Missing mandatory smtp parameter: #{n}"}
|
|
775
|
-
end
|
|
776
|
-
Log.log.debug{"smtp=#{smtp}"}
|
|
777
|
-
return smtp
|
|
778
|
-
end
|
|
779
|
-
|
|
780
|
-
# Send email using ERB template
|
|
781
|
-
# @param email_template_default [String] default template, can be overridden by option
|
|
782
|
-
# @param values [Hash] values to be used in template, keys with default: to, from_name, from_email
|
|
783
|
-
def send_email_template(email_template_default: nil, values: {})
|
|
784
|
-
values[:to] ||= options.get_option(:notify_to, mandatory: true)
|
|
785
|
-
notify_template = options.get_option(:notify_template, mandatory: email_template_default.nil?) || email_template_default
|
|
786
|
-
mail_conf = email_settings
|
|
787
|
-
values[:from_name] ||= mail_conf[:from_name]
|
|
788
|
-
values[:from_email] ||= mail_conf[:from_email]
|
|
789
|
-
%i[to from_email].each do |n|
|
|
790
|
-
Aspera.assert_type(values[n], String){"Missing email parameter: #{n} in config"}
|
|
500
|
+
Log.log.info { "Creating Demo server preset: #{DEMO_PRESET}" }
|
|
501
|
+
cp[DEMO_PRESET] = {
|
|
502
|
+
'url' => "ssh://#{DEMO_SERVER}.asperasoft.com:33001",
|
|
503
|
+
'username' => ASPERA,
|
|
504
|
+
'ssAP'.downcase.reverse + 'drow'.reverse => DEMO_SERVER + ASPERA # cspell:disable-line
|
|
505
|
+
}
|
|
791
506
|
end
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
507
|
+
cp[PresetManager::Key::DEFAULTS] ||= {}
|
|
508
|
+
if cp[PresetManager::Key::DEFAULTS].key?(SERVER_COMMAND)
|
|
509
|
+
Log.log.warn { "Server default preset already set to: #{cp[PresetManager::Key::DEFAULTS][SERVER_COMMAND]}" }
|
|
510
|
+
Log.log.warn { "Use #{DEMO_PRESET} for demo: -P#{DEMO_PRESET}" } unless
|
|
511
|
+
DEMO_PRESET.eql?(cp[PresetManager::Key::DEFAULTS][SERVER_COMMAND])
|
|
512
|
+
else
|
|
513
|
+
cp[PresetManager::Key::DEFAULTS][SERVER_COMMAND] = DEMO_PRESET
|
|
514
|
+
Log.log.info { "Setting server default preset to : #{DEMO_PRESET}" }
|
|
800
515
|
end
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
516
|
+
Result::Status.new('Done')
|
|
517
|
+
end
|
|
518
|
+
|
|
519
|
+
def action_commands
|
|
520
|
+
commands = Plugins::Factory.instance.plugin_list.flat_map do |name|
|
|
521
|
+
plugin_class = Plugins::Factory.instance.plugin_class(name)
|
|
522
|
+
reg = plugin_class.command_registry
|
|
523
|
+
reg.all_paths.reject { |path| reg.children_of(path).any? }.map do |path|
|
|
524
|
+
spec = reg[path]
|
|
525
|
+
# Build syntax by interleaving each path segment with the arguments declared on that node
|
|
526
|
+
tokens = [name.to_s]
|
|
527
|
+
path.each_with_index do |seg, i|
|
|
528
|
+
tokens << seg.to_s
|
|
529
|
+
node_args = reg[path[0, i + 1]]&.arguments.to_a
|
|
530
|
+
node_args.each do |a|
|
|
531
|
+
token = a.allowed ? a.allowed.join('|') : a.name.to_s
|
|
532
|
+
token += '...' if a.multiple
|
|
533
|
+
tokens << (a.mandatory ? "<#{token}>" : "[<#{token}>]")
|
|
534
|
+
end
|
|
535
|
+
end
|
|
536
|
+
syntax = tokens.join(' ')
|
|
537
|
+
{
|
|
538
|
+
syntax: syntax,
|
|
539
|
+
description: spec&.description.to_s
|
|
540
|
+
}
|
|
541
|
+
end
|
|
810
542
|
end
|
|
811
|
-
|
|
812
|
-
end
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
when :create
|
|
826
|
-
vault.set(options.get_next_argument('info', validation: Hash).symbolize_keys)
|
|
827
|
-
return Result::Status.new('Secret added')
|
|
828
|
-
when :delete
|
|
829
|
-
label_to_delete = options.get_next_argument('label')
|
|
830
|
-
vault.delete(label: label_to_delete)
|
|
831
|
-
return Result::Status.new("Secret deleted: #{label_to_delete}")
|
|
832
|
-
when :password
|
|
833
|
-
Aspera.assert(vault.respond_to?(:change_password), 'Vault does not support password change')
|
|
834
|
-
vault.change_password(options.get_next_argument('new_password'))
|
|
835
|
-
return Result::Status.new('Vault password updated')
|
|
543
|
+
Result::ObjectList.new(commands, fields: %w[syntax description])
|
|
544
|
+
end
|
|
545
|
+
|
|
546
|
+
def action_options(plugin_name:, **)
|
|
547
|
+
# Instantiate the plugin so that it registers all its options in context.options
|
|
548
|
+
Plugins::Factory.instance.create(plugin_name.to_sym, context: context)
|
|
549
|
+
rows = context.options.declared_options.map do |sym, opt|
|
|
550
|
+
row = {
|
|
551
|
+
option: "--#{sym.to_s.tr('_', '-')}",
|
|
552
|
+
description: opt.description.to_s
|
|
553
|
+
}
|
|
554
|
+
row[:allowed] = opt.values.join('|') if opt.values&.any?
|
|
555
|
+
row[:deprecated] = opt.deprecation if opt.deprecation
|
|
556
|
+
row
|
|
836
557
|
end
|
|
558
|
+
Result::ObjectList.new(rows, fields: %w[option description allowed deprecated])
|
|
837
559
|
end
|
|
838
560
|
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
raise
|
|
843
|
-
# This raise exception if label not found:
|
|
844
|
-
info = vault.get(label: m[0])
|
|
845
|
-
value = info[m[1].to_sym]
|
|
846
|
-
raise "no such entry value: #{m[1]}" if value.nil?
|
|
847
|
-
return value
|
|
848
|
-
end
|
|
849
|
-
|
|
850
|
-
# @return [Object] vault, from options or cache
|
|
851
|
-
def vault
|
|
852
|
-
return @vault_instance unless @vault_instance.nil?
|
|
853
|
-
info = options.get_option(:vault).symbolize_keys
|
|
854
|
-
info[:type] ||= 'file'
|
|
855
|
-
require 'aspera/keychain/factory'
|
|
856
|
-
@vault_instance = Keychain::Factory.create(
|
|
857
|
-
info,
|
|
858
|
-
Info::CMD_NAME,
|
|
859
|
-
@main_folder,
|
|
860
|
-
options.get_option(:vault_password)
|
|
861
|
-
)
|
|
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
|
|
862
565
|
end
|
|
863
566
|
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
raise type, exception_text
|
|
875
|
-
when :web
|
|
876
|
-
end
|
|
877
|
-
end
|
|
878
|
-
|
|
879
|
-
# Lookup the corresponding secret for the given URL and usernames
|
|
880
|
-
# @param url [String] Server URL
|
|
881
|
-
# @param username [String] Username
|
|
882
|
-
# @return [String, nil] Secret if found
|
|
883
|
-
def lookup_secret(url:, username:)
|
|
884
|
-
secret = options.get_option(:secret)
|
|
885
|
-
if secret.eql?('PRESET')
|
|
886
|
-
conf = presets.lookup_preset(url: url, username: username)
|
|
887
|
-
if conf.is_a?(Hash)
|
|
888
|
-
Log.log.debug{"Found preset #{conf} with URL and username"}
|
|
889
|
-
secret = conf['password']
|
|
567
|
+
def action_completion_bash(words: nil, **)
|
|
568
|
+
if words.nil? || words.empty?
|
|
569
|
+
# Level 0: propose plugin names
|
|
570
|
+
Plugins::Factory.instance.plugin_list.each { |p| puts p }
|
|
571
|
+
else
|
|
572
|
+
plugin_sym = words.first.to_sym
|
|
573
|
+
plugin_class = begin
|
|
574
|
+
Plugins::Factory.instance.plugin_class(plugin_sym)
|
|
575
|
+
rescue StandardError
|
|
576
|
+
Process.exit(0)
|
|
890
577
|
end
|
|
578
|
+
# Navigate into the registry using remaining words as path
|
|
579
|
+
path = words[1..].map(&:to_sym)
|
|
580
|
+
plugin_class.command_registry.children_of(path).each_key { |k| puts k }
|
|
891
581
|
end
|
|
892
|
-
|
|
582
|
+
Process.exit(0)
|
|
893
583
|
end
|
|
584
|
+
|
|
894
585
|
# Folder in $HOME for application files (~/.aspera)
|
|
895
586
|
ASPERA_HOME_FOLDER_NAME = '.aspera'
|
|
896
587
|
# Default config file name
|
|
897
588
|
DEFAULT_CONFIG_FILENAME = 'config.yaml'
|
|
898
|
-
# Used in execute_preset / execute_action to identify the global default keyword
|
|
899
|
-
GLOBAL_DEFAULT_KEYWORD = 'GLOBAL'
|
|
900
589
|
CONF_GLOBAL_SYM = :config
|
|
901
590
|
# Folder containing custom plugins in user's config folder
|
|
902
591
|
ASPERA_PLUGINS_FOLDERNAME = 'plugins'
|
|
@@ -905,7 +594,6 @@ module Aspera
|
|
|
905
594
|
REST_EXCEPTIONS_LOG_FILENAME = 'rest_exceptions.log'
|
|
906
595
|
ASPERA = 'aspera'
|
|
907
596
|
SERVER_COMMAND = 'server'
|
|
908
|
-
TRANSFERD_APP_NAME = 'sdk'
|
|
909
597
|
DEMO_SERVER = 'demo'
|
|
910
598
|
DEMO_PRESET = 'demoserver' # cspell: disable-line
|
|
911
599
|
EMAIL_TEST_TEMPLATE = <<~END_OF_TEMPLATE
|
|
@@ -918,15 +606,9 @@ module Aspera
|
|
|
918
606
|
# Special extended values
|
|
919
607
|
EXTEND_PRESET = :preset
|
|
920
608
|
EXTEND_VAULT = :vault
|
|
921
|
-
EXTEND_ARGS = :''
|
|
922
|
-
DEFAULT_CHECK_NEW_VERSION_DAYS = 7
|
|
923
609
|
COFFEE_IMAGE_URL = 'https://enjoyjava.com/wp-content/uploads/2018/01/How-to-make-strong-coffee.jpg'
|
|
924
|
-
GEM_CHECK_DATE_FMT = '%Y/%m/%d'
|
|
925
|
-
CONF_OVERVIEW_KEYS = %w[preset parameter value].freeze
|
|
926
|
-
SMTP_CONF_PARAMS = %i[server tls ssl port domain username password from_name from_email].freeze
|
|
927
610
|
private_constant :ASPERA_HOME_FOLDER_NAME,
|
|
928
611
|
:DEFAULT_CONFIG_FILENAME,
|
|
929
|
-
:GLOBAL_DEFAULT_KEYWORD,
|
|
930
612
|
:CONF_GLOBAL_SYM,
|
|
931
613
|
:ASPERA_PLUGINS_FOLDERNAME,
|
|
932
614
|
:PERSISTENCY_FOLDER,
|
|
@@ -934,17 +616,12 @@ module Aspera
|
|
|
934
616
|
:REST_EXCEPTIONS_LOG_FILENAME,
|
|
935
617
|
:ASPERA,
|
|
936
618
|
:SERVER_COMMAND,
|
|
937
|
-
:TRANSFERD_APP_NAME,
|
|
938
619
|
:DEMO_SERVER,
|
|
939
620
|
:DEMO_PRESET,
|
|
940
621
|
:EMAIL_TEST_TEMPLATE,
|
|
941
622
|
:EXTEND_PRESET,
|
|
942
623
|
:EXTEND_VAULT,
|
|
943
|
-
:
|
|
944
|
-
:COFFEE_IMAGE_URL,
|
|
945
|
-
:GEM_CHECK_DATE_FMT,
|
|
946
|
-
:CONF_OVERVIEW_KEYS,
|
|
947
|
-
:SMTP_CONF_PARAMS
|
|
624
|
+
:COFFEE_IMAGE_URL
|
|
948
625
|
end
|
|
949
626
|
end
|
|
950
627
|
end
|