aspera-cli 4.26.1 → 4.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +300 -22
- data/CONTRIBUTING.md +100 -12
- data/bin/ascli +5 -5
- data/docs/README.md +10757 -0
- data/docs/test-mcp-with-ai.md +202 -0
- data/lib/aspera/agent/base.rb +20 -6
- data/lib/aspera/agent/connect.rb +53 -16
- data/lib/aspera/agent/desktop.rb +52 -17
- data/lib/aspera/agent/direct.rb +27 -25
- data/lib/aspera/agent/factory.rb +4 -0
- data/lib/aspera/agent/httpgw.rb +2 -2
- data/lib/aspera/agent/node.rb +34 -2
- data/lib/aspera/agent/transferd.rb +39 -3
- data/lib/aspera/api/alee.rb +1 -1
- data/lib/aspera/api/aoc.rb +56 -51
- data/lib/aspera/api/cos_node.rb +2 -2
- data/lib/aspera/api/faspex.rb +22 -20
- data/lib/aspera/api/httpgw.rb +4 -4
- data/lib/aspera/api/node.rb +33 -30
- 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 +25 -23
- data/lib/aspera/ascp/installation.rb +43 -40
- data/lib/aspera/ascp/management.rb +2 -3
- data/lib/aspera/cli/ascp_actions.rb +155 -0
- data/lib/aspera/cli/async_transfer_store.rb +81 -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 +138 -0
- data/lib/aspera/cli/context.rb +71 -0
- data/lib/aspera/cli/error.rb +16 -0
- data/lib/aspera/cli/extended_value.rb +5 -3
- data/lib/aspera/cli/formatter.rb +122 -209
- data/lib/aspera/cli/gem_checker.rb +65 -0
- data/lib/aspera/cli/hints.rb +2 -2
- data/lib/aspera/cli/http.rb +218 -0
- data/lib/aspera/cli/info.rb +2 -0
- data/lib/aspera/cli/mailer.rb +97 -0
- data/lib/aspera/cli/mcp_tool.rb +198 -0
- data/lib/aspera/cli/options.schema.yaml +364 -7
- data/lib/aspera/cli/{manager.rb → parser.rb} +337 -144
- data/lib/aspera/cli/plugins/alee.rb +20 -22
- data/lib/aspera/cli/plugins/aoc.rb +1173 -849
- data/lib/aspera/cli/plugins/ats.rb +200 -161
- data/lib/aspera/cli/plugins/base.rb +470 -107
- data/lib/aspera/cli/plugins/basic_auth.rb +14 -4
- data/lib/aspera/cli/plugins/config.rb +434 -1032
- data/lib/aspera/cli/plugins/console.rb +106 -64
- data/lib/aspera/cli/plugins/cos.rb +44 -32
- data/lib/aspera/cli/plugins/factory.rb +7 -4
- data/lib/aspera/cli/plugins/faspex.rb +296 -259
- data/lib/aspera/cli/plugins/faspex5.rb +592 -344
- data/lib/aspera/cli/plugins/faspio.rb +49 -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 +1001 -797
- data/lib/aspera/cli/plugins/oauth.rb +7 -10
- data/lib/aspera/cli/plugins/orchestrator.rb +111 -139
- data/lib/aspera/cli/plugins/preview.rb +183 -142
- data/lib/aspera/cli/plugins/server.rb +125 -80
- data/lib/aspera/cli/plugins/shares.rb +301 -107
- data/lib/aspera/cli/preset_actions.rb +139 -0
- data/lib/aspera/cli/preset_manager.rb +236 -0
- data/lib/aspera/cli/result.rb +360 -0
- data/lib/aspera/cli/runner.rb +473 -0
- 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 +84 -56
- data/lib/aspera/cli/terminal_formatter.rb +65 -0
- data/lib/aspera/cli/transfer_actions.rb +83 -0
- data/lib/aspera/cli/transfer_agent.rb +116 -51
- data/lib/aspera/cli/transfer_progress.rb +9 -9
- data/lib/aspera/cli/vault_manager.rb +57 -0
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/cli/wizard.rb +26 -26
- data/lib/aspera/command_line_builder.rb +23 -23
- data/lib/aspera/data_repository.rb +6 -1
- data/lib/aspera/dot_container.rb +5 -5
- data/lib/aspera/environment.rb +39 -13
- data/lib/aspera/exec_spec.rb +13 -0
- data/lib/aspera/faspex_gw.rb +2 -2
- data/lib/aspera/faspex_postproc.rb +4 -3
- data/lib/aspera/graphql.rb +35 -0
- data/lib/aspera/hash_ext.rb +6 -0
- data/lib/aspera/json_rpc/client.rb +62 -0
- data/lib/aspera/json_rpc/version.rb +7 -0
- data/lib/aspera/keychain/base.rb +1 -1
- data/lib/aspera/keychain/encrypted_hash.rb +2 -2
- data/lib/aspera/keychain/factory.rb +4 -4
- data/lib/aspera/keychain/macos_security.rb +4 -4
- data/lib/aspera/link_header.rb +82 -0
- data/lib/aspera/log.rb +23 -5
- data/lib/aspera/markdown.rb +85 -2
- data/lib/aspera/nagios.rb +2 -2
- data/lib/aspera/node_simulator.rb +3 -1
- data/lib/aspera/oauth/base.rb +10 -10
- data/lib/aspera/oauth/boot.rb +43 -0
- data/lib/aspera/oauth/factory.rb +41 -15
- data/lib/aspera/oauth/jwt.rb +4 -4
- data/lib/aspera/oauth/web.rb +4 -4
- data/lib/aspera/oauth.rb +1 -0
- data/lib/aspera/persistency_action_once.rb +10 -10
- data/lib/aspera/persistency_folder.rb +10 -3
- data/lib/aspera/preview/file_types.rb +7 -3
- data/lib/aspera/preview/generator.rb +3 -3
- data/lib/aspera/preview/utils.rb +1 -1
- data/lib/aspera/products/connect.rb +4 -1
- data/lib/aspera/products/other.rb +1 -1
- data/lib/aspera/products/transferd.rb +3 -3
- data/lib/aspera/proxy_auto_config.rb +10 -9
- data/lib/aspera/rest.rb +18 -40
- data/lib/aspera/rest_error_analyzer.rb +7 -3
- data/lib/aspera/rest_errors_aspera.rb +0 -10
- data/lib/aspera/rest_list.rb +13 -6
- data/lib/aspera/schema/IBM Aspera Faspex API-5.0-enhanced.yaml +14 -4
- data/lib/aspera/schema/IBM Aspera on Cloud API-0.2.6-enhanced.yaml +2730 -648
- data/lib/aspera/schema/async_tables.yaml +361 -0
- data/lib/aspera/schema/documentation.rb +57 -26
- data/lib/aspera/schema/reader.rb +60 -11
- data/lib/aspera/schema/registry.rb +50 -10
- data/lib/aspera/secret_hider.rb +11 -2
- data/lib/aspera/ssl.rb +4 -4
- data/lib/aspera/sync/conf.schema.yaml +2 -2
- data/lib/aspera/sync/database.rb +14 -10
- data/lib/aspera/sync/operations.rb +15 -18
- data/lib/aspera/temp_file_manager.rb +5 -1
- data/lib/aspera/timer_limiter.rb +1 -1
- data/lib/aspera/transfer/faux_file.rb +24 -11
- data/lib/aspera/transfer/parameters.rb +27 -25
- data/lib/aspera/transfer/result.rb +74 -0
- data/lib/aspera/transfer/resumer.rb +7 -7
- data/lib/aspera/transfer/spec.rb +18 -1
- data/lib/aspera/transfer/spec.schema.yaml +26 -8
- data/lib/aspera/transfer/uri.rb +1 -1
- data/lib/aspera/uri_reader.rb +2 -2
- data/lib/aspera/web_auth.rb +2 -2
- data/lib/aspera/web_server_simple.rb +8 -5
- data/lib/aspera/yaml.rb +2 -1
- data.tar.gz.sig +0 -0
- metadata +45 -16
- metadata.gz.sig +0 -0
- data/lib/aspera/cli/main.rb +0 -480
- data/lib/aspera/json_rpc.rb +0 -51
|
@@ -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,34 +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/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'
|
|
14
19
|
require 'aspera/ascp/installation'
|
|
15
20
|
require 'aspera/sync/operations'
|
|
16
21
|
require 'aspera/products/transferd'
|
|
17
|
-
require 'aspera/transfer/parameters'
|
|
18
22
|
require 'aspera/transfer/spec'
|
|
19
23
|
require 'aspera/schema/documentation'
|
|
20
24
|
require 'aspera/keychain/macos_security'
|
|
21
|
-
require 'aspera/proxy_auto_config'
|
|
22
25
|
require 'aspera/environment'
|
|
23
26
|
require 'aspera/persistency_action_once'
|
|
24
27
|
require 'aspera/id_generator'
|
|
25
|
-
require 'aspera/persistency_folder'
|
|
26
28
|
require 'aspera/data_repository'
|
|
27
|
-
require 'aspera/line_logger'
|
|
28
29
|
require 'aspera/rest'
|
|
29
30
|
require 'aspera/oauth/jwt'
|
|
30
31
|
require 'aspera/log'
|
|
31
32
|
require 'aspera/assert'
|
|
32
|
-
require 'aspera/oauth'
|
|
33
|
-
require 'aspera/ssl'
|
|
34
33
|
require 'openssl'
|
|
35
34
|
require 'digest'
|
|
36
35
|
require 'open3'
|
|
37
|
-
require 'date'
|
|
38
|
-
require 'erb'
|
|
39
36
|
require 'net/http'
|
|
40
37
|
|
|
41
38
|
module Aspera
|
|
@@ -44,6 +41,11 @@ module Aspera
|
|
|
44
41
|
# Manage the CLI config file
|
|
45
42
|
class Config < Base
|
|
46
43
|
include SyncActions
|
|
44
|
+
include VaultManager
|
|
45
|
+
include GemChecker
|
|
46
|
+
include AscpActions
|
|
47
|
+
include PresetActions
|
|
48
|
+
include TransferActions
|
|
47
49
|
|
|
48
50
|
class << self
|
|
49
51
|
# Folder containing plugins in the gem's main folder
|
|
@@ -51,7 +53,7 @@ module Aspera
|
|
|
51
53
|
File.dirname(File.expand_path(__FILE__))
|
|
52
54
|
end
|
|
53
55
|
|
|
54
|
-
# @return main folder where code is, i.e. .../lib
|
|
56
|
+
# @return [String] main folder where code is, i.e. .../lib
|
|
55
57
|
# Go up as many times as englobing modules (not counting class, as it is a file)
|
|
56
58
|
def gem_src_root
|
|
57
59
|
# Module.nesting[2] is Cli::Plugins
|
|
@@ -63,7 +65,7 @@ module Aspera
|
|
|
63
65
|
return Marshal.load(Marshal.dump(val))
|
|
64
66
|
end
|
|
65
67
|
|
|
66
|
-
# @return product family folder (~/.aspera)
|
|
68
|
+
# @return [String] product family folder (~/.aspera)
|
|
67
69
|
def module_family_folder
|
|
68
70
|
user_home_folder = Dir.home
|
|
69
71
|
Aspera.assert(Dir.exist?(user_home_folder), type: Cli::Error){"Home folder does not exist: #{user_home_folder}. Check your user environment."}
|
|
@@ -73,416 +75,280 @@ module Aspera
|
|
|
73
75
|
# @return [String] Product config folder (~/.aspera/<name>)
|
|
74
76
|
def default_app_main_folder(app_name:)
|
|
75
77
|
Aspera.assert_type(app_name, String)
|
|
76
|
-
Aspera.assert(!app_name.empty
|
|
78
|
+
Aspera.assert(!app_name.empty?, 'app_name must not be empty')
|
|
77
79
|
return File.join(module_family_folder, app_name)
|
|
78
80
|
end
|
|
79
81
|
end
|
|
80
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: Allowed::TYPES_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: Allowed::TYPES_BOOLEAN, default: true, handler: :option_cache_tokens
|
|
96
|
+
|
|
81
97
|
def initialize(**_)
|
|
82
|
-
# We need to defer parsing of options until we have the config file, so we can use @extend with @preset
|
|
83
98
|
super
|
|
84
|
-
@use_plugin_defaults = true
|
|
85
|
-
@config_presets = {}
|
|
86
|
-
@config_checksum_on_disk = nil
|
|
87
99
|
@vault_instance = nil
|
|
88
|
-
@pac_exec = nil
|
|
89
100
|
@sdk_default_location = false
|
|
90
|
-
@option_insecure = false
|
|
91
|
-
@option_warn_insecure_cert = true
|
|
92
|
-
@option_ignore_cert_host_port = []
|
|
93
|
-
@option_http_options = {}
|
|
94
|
-
@ssl_warned_urls = []
|
|
95
101
|
@option_cache_tokens = true
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
#
|
|
99
|
-
@
|
|
100
|
-
# Paths are used for ascp
|
|
101
|
-
@certificate_paths = nil
|
|
102
|
-
@progress_bar = nil
|
|
103
|
-
# Option to set main folder
|
|
104
|
-
options.declare(
|
|
105
|
-
:home, 'Home folder for tool',
|
|
106
|
-
handler: {o: self, m: :main_folder},
|
|
107
|
-
default: self.class.default_app_main_folder(app_name: Info::CMD_NAME)
|
|
108
|
-
)
|
|
109
|
-
options.parse_options!
|
|
110
|
-
Log.log.debug{"#{Info::CMD_NAME} folder: #{@main_folder}"}
|
|
111
|
-
setup_persistency_and_plugin_folders
|
|
112
|
-
# Option to set config file
|
|
113
|
-
options.declare(
|
|
114
|
-
:config_file, 'Path to YAML file with preset configuration',
|
|
115
|
-
handler: {o: self, m: :option_config_file},
|
|
116
|
-
default: File.join(@main_folder, DEFAULT_CONFIG_FILENAME)
|
|
117
|
-
)
|
|
118
|
-
options.parse_options!
|
|
119
|
-
# Read config file (set @config_presets)
|
|
120
|
-
read_config_file
|
|
121
|
-
setup_extended_value_handlers
|
|
122
|
-
# Vault options
|
|
123
|
-
options.declare(:secret, 'Secret for access keys')
|
|
124
|
-
options.declare(:vault, 'Vault for secrets', allowed: Hash)
|
|
125
|
-
options.declare(:vault_password, 'Vault password')
|
|
126
|
-
options.parse_options!
|
|
127
|
-
# Declare generic plugin options only after handlers are declared
|
|
128
|
-
Base.declare_options(options)
|
|
129
|
-
# Configuration options
|
|
130
|
-
options.declare(:no_default, 'Do not load default configuration for plugin', allowed: Allowed::TYPES_NONE, short: 'N'){@use_plugin_defaults = false}
|
|
131
|
-
options.declare(:preset, 'Load the named option preset from current config file', short: 'P', handler: {o: self, m: :option_preset})
|
|
132
|
-
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)
|
|
133
|
-
options.declare(:plugin_folder, 'Folder where to find additional plugins', handler: {o: self, m: :option_plugin_folder})
|
|
134
|
-
# Declare wizard options
|
|
135
|
-
@wizard = Wizard.new(self, @main_folder)
|
|
136
|
-
# Transfer SDK options
|
|
137
|
-
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: Allowed::TYPES_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)
|
|
138
106
|
options.parse_options!
|
|
139
107
|
set_sdk_dir
|
|
140
|
-
options
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
# Email options
|
|
144
|
-
options.declare(:smtp, 'Email: SMTP configuration', allowed: Hash)
|
|
145
|
-
options.declare(:notify_to, 'Email: Recipient for notification of transfers')
|
|
146
|
-
options.declare(:notify_template, 'Email: ERB template for notification of transfers')
|
|
147
|
-
# HTTP options
|
|
148
|
-
options.declare(:insecure, 'HTTP/S: Do not validate any certificate', allowed: Allowed::TYPES_BOOLEAN, handler: {o: self, m: :option_insecure}, default: false)
|
|
149
|
-
options.declare(:ignore_certificate, 'HTTP/S: Do not validate certificate for these URLs', allowed: [Array, NilClass], handler: {o: self, m: :option_ignore_cert_host_port})
|
|
150
|
-
options.declare(:warn_insecure, 'HTTP/S: Issue a warning if certificate is ignored', allowed: Allowed::TYPES_BOOLEAN, handler: {o: self, m: :option_warn_insecure_cert}, default: true)
|
|
151
|
-
options.declare(:cert_stores, 'HTTP/S: List of folder with trusted certificates', allowed: Allowed::TYPES_STRING_ARRAY, handler: {o: self, m: :trusted_cert_locations})
|
|
152
|
-
options.declare(:http_options, 'HTTP/S: Options for HTTP/S socket', allowed: Hash, handler: {o: self, m: :option_http_options}, default: {})
|
|
153
|
-
options.declare(:http_proxy, 'HTTP/S: URL for proxy with optional credentials', handler: {o: self, m: :option_http_proxy})
|
|
154
|
-
options.declare(:cache_tokens, 'Save and reuse OAuth tokens', allowed: Allowed::TYPES_BOOLEAN, handler: {o: self, m: :option_cache_tokens})
|
|
155
|
-
options.declare(:fpac, 'Proxy auto configuration script')
|
|
156
|
-
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)
|
|
157
111
|
options.parse_options!
|
|
158
|
-
@progress_bar = TransferProgress.new if options.get_option(:progress_bar)
|
|
159
|
-
setup_pac_executor
|
|
160
|
-
setup_rest_and_transfer_runtime
|
|
161
|
-
end
|
|
162
|
-
|
|
163
|
-
private
|
|
164
|
-
|
|
165
|
-
def setup_persistency_and_plugin_folders
|
|
166
|
-
context.persistency = PersistencyFolder.new(File.join(@main_folder, PERSISTENCY_FOLDER))
|
|
167
|
-
Plugins::Factory.instance.add_lookup_folder(self.class.gem_plugins_folder)
|
|
168
|
-
Plugins::Factory.instance.add_lookup_folder(File.join(@main_folder, ASPERA_PLUGINS_FOLDERNAME))
|
|
169
|
-
end
|
|
170
|
-
|
|
171
|
-
def setup_extended_value_handlers
|
|
172
|
-
ExtendedValue.instance.on(EXTEND_PRESET){ |v| preset_by_name(v)}
|
|
173
|
-
ExtendedValue.instance.on(EXTEND_VAULT){ |v| vault_value(v)}
|
|
174
|
-
ExtendedValue.instance.on(EXTEND_ARGS){ |v| options.args_as_extended(v)}
|
|
175
|
-
add_plugin_default_preset(CONF_GLOBAL_SYM)
|
|
176
|
-
end
|
|
177
|
-
|
|
178
|
-
def setup_pac_executor
|
|
179
|
-
pac_script = options.get_option(:fpac)
|
|
180
|
-
return unless pac_script
|
|
181
|
-
|
|
182
|
-
@pac_exec = ProxyAutoConfig.new(pac_script).register_uri_generic
|
|
183
|
-
proxy_user_pass = options.get_option(:proxy_credentials)
|
|
184
|
-
if proxy_user_pass
|
|
185
|
-
Aspera.assert(proxy_user_pass.length.eql?(2), type: Cli::BadArgument){"proxy_credentials shall have two elements (#{proxy_user_pass.length})"}
|
|
186
|
-
@pac_exec.proxy_user = proxy_user_pass[0]
|
|
187
|
-
@pac_exec.proxy_pass = proxy_user_pass[1]
|
|
188
|
-
end
|
|
189
|
-
end
|
|
190
|
-
|
|
191
|
-
def setup_rest_and_transfer_runtime
|
|
192
|
-
RestParameters.instance.user_agent = Info::CMD_NAME
|
|
193
|
-
RestParameters.instance.progress_bar = @progress_bar
|
|
194
|
-
RestParameters.instance.session_cb = ->(http_session){update_http_session(http_session)}
|
|
195
|
-
RestParameters.instance.spinner_cb = ->(title = nil, action: :spin){formatter.long_operation(title, action: action)}
|
|
196
|
-
# Check http options that are global
|
|
197
|
-
keys_to_delete = []
|
|
198
|
-
@option_http_options.each do |k, v|
|
|
199
|
-
method = "#{k}=".to_sym
|
|
200
|
-
if RestParameters.instance.respond_to?(method)
|
|
201
|
-
keys_to_delete.push(k)
|
|
202
|
-
RestParameters.instance.send(method, v)
|
|
203
|
-
elsif k.eql?('ssl_options')
|
|
204
|
-
keys_to_delete.push(k)
|
|
205
|
-
Aspera::SSL.option_list = v
|
|
206
|
-
elsif OAuth::Factory.instance.parameters.key?(k.to_sym)
|
|
207
|
-
keys_to_delete.push(k)
|
|
208
|
-
OAuth::Factory.instance.parameters[k.to_sym] = v
|
|
209
|
-
end
|
|
210
|
-
end
|
|
211
|
-
keys_to_delete.each{ |k| @option_http_options.delete(k)}
|
|
212
|
-
OAuth::Factory.instance.persist_mgr = persistency if @option_cache_tokens
|
|
213
|
-
OAuth::Web.additional_info = "#{Info::CMD_NAME} v#{Cli::VERSION}"
|
|
214
|
-
Transfer::Parameters.file_list_folder = File.join(@main_folder, FILE_LIST_FOLDER_NAME)
|
|
215
|
-
RestErrorAnalyzer.instance.log_file = File.join(@main_folder, REST_EXCEPTIONS_LOG_FILENAME)
|
|
216
|
-
# Register aspera REST call error handlers
|
|
217
|
-
RestErrorsAspera.register_handlers
|
|
218
|
-
end
|
|
219
|
-
|
|
220
|
-
public
|
|
221
|
-
|
|
222
|
-
attr_accessor :main_folder, :option_cache_tokens, :option_insecure, :option_warn_insecure_cert, :option_http_options
|
|
223
|
-
attr_reader :option_ignore_cert_host_port, :progress_bar
|
|
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
|
|
243
|
-
end
|
|
244
|
-
end
|
|
245
|
-
|
|
246
|
-
# Add files, folders or default locations to the certificate store
|
|
247
|
-
# @param path_list [Array<String>] List of paths to add
|
|
248
|
-
# @return the list of paths
|
|
249
|
-
def trusted_cert_locations=(path_list)
|
|
250
|
-
Aspera.assert_type(path_list, Array){'cert locations'}
|
|
251
|
-
if @certificate_store.nil?
|
|
252
|
-
Log.log.debug('Creating SSL Cert store')
|
|
253
|
-
@certificate_store = OpenSSL::X509::Store.new
|
|
254
|
-
@certificate_paths = []
|
|
255
|
-
end
|
|
256
|
-
|
|
257
|
-
path_list.each do |path|
|
|
258
|
-
Aspera.assert_type(path, String){'Expecting a String for certificate location'}
|
|
259
|
-
paths_to_add = [path]
|
|
260
|
-
Log.log.debug{"Adding cert location: #{path}"}
|
|
261
|
-
if path.eql?(SpecialValues::DEF)
|
|
262
|
-
@certificate_store.set_default_paths
|
|
263
|
-
paths_to_add = [OpenSSL::X509::DEFAULT_CERT_DIR]
|
|
264
|
-
# JRuby cert file seems not to be PEM
|
|
265
|
-
paths_to_add.push(OpenSSL::X509::DEFAULT_CERT_FILE) unless defined?(JRUBY_VERSION)
|
|
266
|
-
paths_to_add.select!{ |f| File.exist?(f)}
|
|
267
|
-
elsif File.file?(path)
|
|
268
|
-
@certificate_store.add_file(path)
|
|
269
|
-
elsif File.directory?(path)
|
|
270
|
-
@certificate_store.add_path(path)
|
|
271
|
-
else
|
|
272
|
-
raise "No such file or folder: #{path}"
|
|
273
|
-
end
|
|
274
|
-
paths_to_add.each do |p|
|
|
275
|
-
pp = [File.realpath(p)]
|
|
276
|
-
if File.directory?(p)
|
|
277
|
-
pp = Dir.entries(p)
|
|
278
|
-
.map{ |e| File.realpath(File.join(p, e))}
|
|
279
|
-
.select{ |entry| File.file?(entry)}
|
|
280
|
-
.select{ |entry| CERT_EXT.any?{ |ext| entry.end_with?(ext)}}
|
|
281
|
-
end
|
|
282
|
-
@certificate_paths.concat(pp)
|
|
283
|
-
end
|
|
284
|
-
end
|
|
285
|
-
@certificate_paths.uniq!
|
|
286
|
-
end
|
|
287
|
-
|
|
288
|
-
# @return only files
|
|
289
|
-
def trusted_cert_locations
|
|
290
|
-
locations = @certificate_paths
|
|
291
|
-
if locations.nil?
|
|
292
|
-
# Compute default locations
|
|
293
|
-
self.trusted_cert_locations = [SpecialValues::DEF]
|
|
294
|
-
locations = @certificate_paths
|
|
295
|
-
# Restore defaults
|
|
296
|
-
@certificate_paths = @certificate_store = nil
|
|
297
|
-
end
|
|
298
|
-
return locations
|
|
299
112
|
end
|
|
300
113
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
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(
|
|
131
|
+
:set, description: 'Set a parameter in a preset',
|
|
132
|
+
arguments: [{name: :name, type: :identifier}, {name: :param_name, type: String},
|
|
133
|
+
{name: :param_value, type: nil}]
|
|
134
|
+
)
|
|
135
|
+
command :initialize, description: 'Initialize a preset with a value',
|
|
136
|
+
arguments: [{name: :name, type: :identifier}, {name: :config_value, type: Hash}]
|
|
137
|
+
command :update, description: 'Update a preset with current option values',
|
|
138
|
+
arguments: [{name: :name, type: :identifier}]
|
|
139
|
+
command :ask, description: 'Interactively ask for option values',
|
|
140
|
+
arguments: [{name: :name, type: :identifier},
|
|
141
|
+
{name: :option_names, type: String, multiple: true, interactive: true}]
|
|
142
|
+
end
|
|
143
|
+
command(
|
|
144
|
+
:open, description: 'Open the configuration file in the default editor',
|
|
145
|
+
action: lambda do
|
|
146
|
+
Environment.instance.open_editor(context.presets.config_file.to_s)
|
|
147
|
+
Result::Nothing.new
|
|
148
|
+
end
|
|
149
|
+
)
|
|
150
|
+
command :documentation, description: 'Open the documentation in the default browser',
|
|
151
|
+
arguments: [
|
|
152
|
+
{name: :location, type: Symbol, mandatory: false, default: :github, allowed: %i[github local toc]},
|
|
153
|
+
{name: :section, type: String, mandatory: false}
|
|
154
|
+
]
|
|
155
|
+
command :genkey, description: 'Generate a new RSA private key',
|
|
156
|
+
arguments: [
|
|
157
|
+
{name: :private_key_path, type: String},
|
|
158
|
+
{name: :private_key_length, type: Integer, mandatory: false, default: OAuth::Jwt::DEFAULT_PRIV_KEY_LENGTH}
|
|
159
|
+
]
|
|
160
|
+
command :pubkey, description: 'Display the public key of an RSA private key',
|
|
161
|
+
arguments: [{name: :private_key_pem, type: String}],
|
|
162
|
+
action: ->(private_key_pem:, **){Result::Text.new(OpenSSL::PKey::RSA.new(private_key_pem).public_key.to_s)}
|
|
163
|
+
command :remote_certificate, description: 'Retrieve the certificate chain of a remote HTTPS server'
|
|
164
|
+
command :echo, description: 'Display the value of a given argument',
|
|
165
|
+
arguments: [{name: :value, type: nil}],
|
|
166
|
+
action: ->(value:, **){Result.auto(value)}
|
|
167
|
+
command :download, description: 'Download a file from a URL',
|
|
168
|
+
arguments: [
|
|
169
|
+
{name: :file_url, type: String},
|
|
170
|
+
{name: :file_dest, type: String, mandatory: false}
|
|
171
|
+
]
|
|
172
|
+
command :tokens, description: 'Manage OAuth tokens'
|
|
173
|
+
command :plugins, description: 'Manage CLI plugins'
|
|
174
|
+
command :detect, description: 'Detect the Aspera product from a URL (interactive)',
|
|
175
|
+
arguments: [{name: :url, type: String}, {name: :plugin_name, mandatory: false, default: nil}]
|
|
176
|
+
command :wizard, description: 'Run the setup wizard for an Aspera product (interactive)',
|
|
177
|
+
arguments: [{name: :url, type: String}, {name: :plugin_name, mandatory: false, default: nil},
|
|
178
|
+
{name: :preset_name, mandatory: false, default: ''}]
|
|
179
|
+
command :coffee, description: 'Display a coffee image', action: ->{Result::Image.new(COFFEE_IMAGE_URL)}
|
|
180
|
+
command :image, description: 'Display an image',
|
|
181
|
+
arguments: [{name: :image_uri, type: nil}],
|
|
182
|
+
action: ->(image_uri:, **){Result::Image.new(image_uri)}
|
|
183
|
+
command :sync, description: 'Manage Aspera Sync operations'
|
|
184
|
+
command :gem, description: 'Display gem information'
|
|
185
|
+
command :folder, description: 'Display the configuration folder path', action: ->{Result::Text.new(context.main_folder)}
|
|
186
|
+
command :file, description: 'Display the configuration file path', action: ->{Result::Text.new(context.presets.config_file)}
|
|
187
|
+
command(
|
|
188
|
+
:email_test, description: 'Send a test email',
|
|
189
|
+
action: lambda do
|
|
190
|
+
context.mailer.send_email_template(email_template_default: EMAIL_TEST_TEMPLATE)
|
|
191
|
+
Result::Nothing.new
|
|
192
|
+
end
|
|
193
|
+
)
|
|
194
|
+
command :smtp_settings, description: 'Display the current SMTP settings', action: ->{Result::SingleObject.new(context.mailer.email_settings)}
|
|
195
|
+
command(
|
|
196
|
+
:proxy_check, description: 'Check the proxy returned by the PAC script for a given URL',
|
|
197
|
+
arguments: [{name: :server_url, type: String}],
|
|
198
|
+
action: lambda do |server_url:, **|
|
|
199
|
+
Aspera.assert(!context.pac_executor.nil?, type: Cli::BadArgument){'No PAC script configured, use --fpac'}
|
|
200
|
+
Result::ValueList.new(context.pac_executor.get_proxies(server_url), name: 'proxy')
|
|
201
|
+
end
|
|
202
|
+
)
|
|
203
|
+
command :check_update, description: 'Check if a newer version of the gem is available', action: ->{Result::SingleObject.new(check_gem_version)}
|
|
204
|
+
command :initdemo, description: 'Initialize the demo server preset'
|
|
205
|
+
command :vault, description: 'Manage secrets in the vault'
|
|
206
|
+
commands_under(:vault) do
|
|
207
|
+
command :info, description: 'Display vault information',
|
|
208
|
+
action: ->(**){Result::SingleObject.new(vault.info)}
|
|
209
|
+
command :list, description: 'List secrets in the vault',
|
|
210
|
+
action: ->(**){Result::ObjectList.new(vault.list)}
|
|
211
|
+
command :show, description: 'Show a secret by label',
|
|
212
|
+
arguments: [{name: :label, type: String}]
|
|
213
|
+
command :create, description: 'Add a new secret to the vault',
|
|
214
|
+
arguments: [{name: :info, type: Hash}]
|
|
215
|
+
command :delete, description: 'Delete a secret by label',
|
|
216
|
+
arguments: [{name: :label, type: String}]
|
|
217
|
+
command :password, description: 'Change the vault password',
|
|
218
|
+
arguments: [{name: :new_password, type: String}]
|
|
219
|
+
end
|
|
220
|
+
command :commands, description: 'List all available commands across all plugins'
|
|
221
|
+
command :options, description: 'List all options available for a plugin',
|
|
222
|
+
arguments: [{name: :plugin_name, type: String}]
|
|
223
|
+
command :test, description: 'Internal test commands'
|
|
224
|
+
command :platform, description: 'Display the current platform/architecture', action: ->{Result::Text.new(Environment.instance.architecture)}
|
|
225
|
+
command :completion, description: 'Generate shell completion scripts'
|
|
226
|
+
|
|
227
|
+
# remote_certificate sub-commands
|
|
228
|
+
commands_under(:remote_certificate) do
|
|
229
|
+
command :chain, description: 'Display the full certificate chain as PEM',
|
|
230
|
+
arguments: [{name: :remote_url, type: String}]
|
|
231
|
+
command :only, description: 'Display only the server certificate as PEM',
|
|
232
|
+
arguments: [{name: :remote_url, type: String}]
|
|
233
|
+
command :name, description: 'Display the CN of the server certificate',
|
|
234
|
+
arguments: [{name: :remote_url, type: String}]
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# tokens sub-commands
|
|
238
|
+
commands_under(:tokens) do
|
|
239
|
+
command(
|
|
240
|
+
:flush, description: 'Delete all cached OAuth tokens',
|
|
241
|
+
action: lambda do
|
|
242
|
+
require 'aspera/api/node'
|
|
243
|
+
Result::ValueList.new(OAuth::Factory.instance.flush_tokens, name: 'file')
|
|
351
244
|
end
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
Rest.new(base_url: 'https://rubygems.org/api/v1').read("versions/#{Info::GEM_NAME}/latest.json")['version']
|
|
359
|
-
rescue StandardError
|
|
360
|
-
Log.log.warn('Could not retrieve latest gem version on rubygems.')
|
|
361
|
-
'0'
|
|
245
|
+
)
|
|
246
|
+
command(
|
|
247
|
+
:list, description: 'List all cached OAuth tokens',
|
|
248
|
+
action: lambda do
|
|
249
|
+
require 'aspera/api/node'
|
|
250
|
+
Result::ObjectList.new(OAuth::Factory.instance.persisted_tokens)
|
|
362
251
|
end
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
252
|
+
)
|
|
253
|
+
command :show, description: 'Show details of a cached OAuth token',
|
|
254
|
+
arguments: [{name: :token_id, type: :identifier}]
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# plugins sub-commands
|
|
258
|
+
commands_under(:plugins) do
|
|
259
|
+
command :list, description: 'List all available plugins'
|
|
260
|
+
command :create, description: 'Create a new plugin skeleton file',
|
|
261
|
+
arguments: [
|
|
262
|
+
{name: :name, type: String},
|
|
263
|
+
{name: :folder, type: String, mandatory: false}
|
|
264
|
+
]
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
# ascp sub-commands
|
|
268
|
+
command :ascp, description: 'Manage FASP/ascp transfer engine'
|
|
269
|
+
commands_under(:ascp) do
|
|
270
|
+
command :show, description: 'Display ascp binary path'
|
|
271
|
+
command :info, description: 'Display ascp and transfer spec information'
|
|
272
|
+
command :install, description: 'Install the transfer SDK',
|
|
273
|
+
arguments: [{name: :version, mandatory: false, default: nil}]
|
|
274
|
+
command :spec, description: 'Display the transfer spec schema'
|
|
275
|
+
command :schema, description: 'Display the transfer spec JSON schema',
|
|
276
|
+
arguments: [{name: :agent_name, mandatory: false, default: nil}]
|
|
277
|
+
command :errors, description: 'Display FASP error codes'
|
|
278
|
+
command :products, description: 'Manage installed Aspera products'
|
|
279
|
+
commands_under(%i[ascp products]) do
|
|
280
|
+
command :list, description: 'List installed Aspera products'
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
# agents sub-commands
|
|
285
|
+
command :agents, description: 'Manage transfer agents'
|
|
286
|
+
commands_under(:agents) do
|
|
287
|
+
command :list, description: 'List all transfer agents'
|
|
288
|
+
command :show, description: 'Show details for a transfer agent',
|
|
289
|
+
arguments: [{name: :agent_name, allowed: Agent::Factory::ALL.keys}]
|
|
290
|
+
command :parameters, description: 'Show configurable parameters for a transfer agent',
|
|
291
|
+
arguments: [{name: :agent_name, allowed: Agent::Factory::ALL.keys}]
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
# transfer async management sub-commands
|
|
295
|
+
command :transfer, description: 'Manage asynchronous transfers'
|
|
296
|
+
commands_under(:transfer) do
|
|
297
|
+
command :list, description: 'List all async transfer jobs'
|
|
298
|
+
command :status, description: 'Show status of an async transfer job',
|
|
299
|
+
arguments: [{name: :job_id, type: String}]
|
|
300
|
+
command :cleanup, description: 'Remove completed/failed/cancelled transfer entries'
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
# transferd sub-commands
|
|
304
|
+
command :transferd, description: 'Manage the transfer daemon (transferd)'
|
|
305
|
+
commands_under(:transferd) do
|
|
306
|
+
command :install, description: 'Install the transfer daemon'
|
|
307
|
+
command :list, description: 'List available SDK locations'
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
# sync sub-commands
|
|
311
|
+
commands_under(:sync) do
|
|
312
|
+
command(
|
|
313
|
+
:spec, description: 'Display the sync configuration schema',
|
|
314
|
+
action: lambda do
|
|
315
|
+
builder = Schema::Documentation.new(TerminalFormatter, Sync::Operations::CONF_SCHEMA, include_option: true).build
|
|
316
|
+
Result::ObjectList.new(builder.rows, fields: builder.columns)
|
|
367
317
|
end
|
|
368
|
-
end
|
|
369
|
-
return {
|
|
370
|
-
name: Info::GEM_NAME,
|
|
371
|
-
current: Cli::VERSION,
|
|
372
|
-
latest: latest_version,
|
|
373
|
-
need_update: Gem::Version.new(Cli::VERSION) < Gem::Version.new(latest_version)
|
|
374
|
-
}
|
|
375
|
-
end
|
|
376
|
-
|
|
377
|
-
def periodic_check_newer_gem_version
|
|
378
|
-
# Get verification period
|
|
379
|
-
delay_days = options.get_option(:version_check_days, mandatory: true).to_i
|
|
380
|
-
# Check only if not zero day
|
|
381
|
-
return if delay_days.eql?(0)
|
|
382
|
-
# Get last date from persistency
|
|
383
|
-
last_check_array = []
|
|
384
|
-
check_date_persist = PersistencyActionOnce.new(
|
|
385
|
-
manager: persistency,
|
|
386
|
-
data: last_check_array,
|
|
387
|
-
id: 'version_last_check'
|
|
388
318
|
)
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
# Generate timestamp
|
|
395
|
-
last_check_array[0] = current_date.strftime(GEM_CHECK_DATE_FMT)
|
|
396
|
-
check_date_persist.save
|
|
397
|
-
# Compare this version and the one on internet
|
|
398
|
-
check_data = check_gem_version
|
|
399
|
-
Log.log.warn do
|
|
400
|
-
"A new version is available: #{check_data[:latest]}. You have #{check_data[:current]}. Upgrade with: gem update #{check_data[:name]}"
|
|
401
|
-
end if check_data[:need_update]
|
|
319
|
+
command :admin, description: 'Manage sync database (admin operations)'
|
|
320
|
+
SyncActions.register_sync_admin_commands(self, %i[sync admin])
|
|
321
|
+
command :translate, description: 'Translate async-style arguments to sync config format',
|
|
322
|
+
arguments: [{name: :async_arguments, type: String, multiple: true}],
|
|
323
|
+
action: ->(async_arguments:, **){Result::SingleObject.new(Sync::Operations.args_to_conf(async_arguments))}
|
|
402
324
|
end
|
|
403
325
|
|
|
404
|
-
#
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
default_config_name = get_plugin_default_config_name(plugin_name_sym)
|
|
410
|
-
Log.log.debug{"add_plugin_default_preset:#{plugin_name_sym}:#{default_config_name}"}
|
|
411
|
-
options.add_option_preset(preset_by_name(default_config_name), 'default_plugin', override: false) unless default_config_name.nil?
|
|
412
|
-
return
|
|
326
|
+
# gem sub-commands
|
|
327
|
+
commands_under(:gem) do
|
|
328
|
+
command :path, description: 'Display the gem source root path', action: ->{Result::Text.new(self.class.gem_src_root)}
|
|
329
|
+
command :version, description: 'Display the gem version', action: ->{Result::Text.new(Cli::VERSION)}
|
|
330
|
+
command :name, description: 'Display the gem name', action: ->{Result::Text.new(Info::GEM_NAME)}
|
|
413
331
|
end
|
|
414
332
|
|
|
415
|
-
#
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
end
|
|
424
|
-
|
|
425
|
-
def defaults_set(plugin_name, preset_name, preset_values, option_default, option_override)
|
|
426
|
-
@config_presets[CONF_PRESET_DEFAULTS] ||= {}
|
|
427
|
-
raise Cli::Error, "A default configuration already exists for plugin '#{plugin_name}' (use --override=yes or --default=no)" \
|
|
428
|
-
if !option_override && option_default && @config_presets[CONF_PRESET_DEFAULTS].key?(plugin_name)
|
|
429
|
-
raise Cli::Error, "Preset already exists: #{preset_name} (use --override=yes or provide alternate name on command line)" \
|
|
430
|
-
if !option_override && @config_presets.key?(preset_name)
|
|
431
|
-
if option_default
|
|
432
|
-
Log.log.info("Setting config preset as default for #{plugin_name}")
|
|
433
|
-
@config_presets[CONF_PRESET_DEFAULTS][plugin_name.to_s] = preset_name
|
|
434
|
-
end
|
|
435
|
-
@config_presets[preset_name] = preset_values
|
|
333
|
+
# test sub-commands
|
|
334
|
+
commands_under(:test) do
|
|
335
|
+
command :throw, description: 'Raise an exception (for testing)',
|
|
336
|
+
arguments: [
|
|
337
|
+
{name: :exception_class_name, type: String},
|
|
338
|
+
{name: :exception_text, type: String}
|
|
339
|
+
]
|
|
340
|
+
command :web, description: 'Test web browser interaction', action: -> {}
|
|
436
341
|
end
|
|
437
342
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
if selected_preset.nil?
|
|
443
|
-
Log.log.debug{"Unknown preset name: #{preset}, initializing"}
|
|
444
|
-
selected_preset = @config_presets[preset] = {}
|
|
445
|
-
end
|
|
446
|
-
Aspera.assert_type(selected_preset, Hash){"#{preset}.#{param_name}"}
|
|
447
|
-
if selected_preset.key?(param_name)
|
|
448
|
-
if selected_preset[param_name].eql?(param_value)
|
|
449
|
-
Log.log.warn{"keeping same value for #{preset}: #{param_name}: #{param_value}"}
|
|
450
|
-
return
|
|
451
|
-
end
|
|
452
|
-
Log.log.warn{"overwriting value for #{param_name}: #{selected_preset[param_name]}"}
|
|
453
|
-
end
|
|
454
|
-
selected_preset[param_name] = param_value
|
|
455
|
-
Log.log.info("Updated: #{preset}: #{param_name} <- #{param_value}")
|
|
456
|
-
nil
|
|
343
|
+
# completion sub-commands
|
|
344
|
+
commands_under(:completion) do
|
|
345
|
+
command :bash, description: 'Generate bash completion script',
|
|
346
|
+
arguments: [{name: :words, type: String, multiple: true, mandatory: false}]
|
|
457
347
|
end
|
|
458
348
|
|
|
459
|
-
|
|
460
|
-
# Creates one if none already created
|
|
461
|
-
# @return preset name that contains global default
|
|
462
|
-
def set_global_default(key, value)
|
|
463
|
-
set_preset_key(global_default_preset, key, value)
|
|
464
|
-
end
|
|
349
|
+
attr_accessor :option_cache_tokens
|
|
465
350
|
|
|
466
|
-
# $HOME/.aspera/`program_name`
|
|
467
351
|
attr_reader :gem_url
|
|
468
|
-
attr_accessor :option_config_file
|
|
469
|
-
|
|
470
|
-
# @param config_name name of the preset in config file
|
|
471
|
-
# @param include_path used to detect and avoid include loops
|
|
472
|
-
# @return copy of the hash from name (also expands possible includes)
|
|
473
|
-
def preset_by_name(config_name, include_path = [])
|
|
474
|
-
raise Cli::Error, 'loop in include' if include_path.include?(config_name)
|
|
475
|
-
include_path = include_path.clone # Avoid messing up if there are multiple branches
|
|
476
|
-
current = @config_presets
|
|
477
|
-
config_name.split(PRESET_DIG_SEPARATOR).each do |name|
|
|
478
|
-
Aspera.assert_type(current, Hash, type: Cli::Error){"sub key: #{include_path}"}
|
|
479
|
-
include_path.push(name)
|
|
480
|
-
current = current[name]
|
|
481
|
-
raise Cli::Error, "Unknown config preset: #{include_path}" if current.nil?
|
|
482
|
-
end
|
|
483
|
-
current = self.class.deep_clone(current) unless current.is_a?(String)
|
|
484
|
-
return ExtendedValue.instance.evaluate(current, context: 'preset')
|
|
485
|
-
end
|
|
486
352
|
|
|
487
353
|
def option_plugin_folder=(value)
|
|
488
354
|
value = [value] unless value.is_a?(Array)
|
|
@@ -501,663 +367,222 @@ module Aspera
|
|
|
501
367
|
when Hash
|
|
502
368
|
options.add_option_preset(value, 'set')
|
|
503
369
|
when String
|
|
504
|
-
options.add_option_preset(
|
|
370
|
+
options.add_option_preset(presets.by_name(value), 'set_by_name')
|
|
505
371
|
else
|
|
506
372
|
raise BadArgument, 'Preset definition must be a String for preset name, or Hash for set of values'
|
|
507
373
|
end
|
|
508
374
|
end
|
|
509
375
|
|
|
510
|
-
#
|
|
511
|
-
def config_checksum
|
|
512
|
-
Digest::SHA1.hexdigest(JSON.generate(@config_presets))
|
|
513
|
-
end
|
|
376
|
+
# DSL handlers - one method per leaf command
|
|
514
377
|
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
@config_presets = YAML.load_file(conf_file_to_load)
|
|
530
|
-
@config_checksum_on_disk = config_checksum
|
|
531
|
-
end
|
|
532
|
-
files_to_copy = []
|
|
533
|
-
Log.dump(:available_presets, @config_presets, level: :trace1)
|
|
534
|
-
Aspera.assert_type(@config_presets, Hash){'config file YAML'}
|
|
535
|
-
# Check there is at least the config section
|
|
536
|
-
Aspera.assert(@config_presets.key?(CONF_PRESET_CONFIG)){"Cannot find key: #{CONF_PRESET_CONFIG}"}
|
|
537
|
-
version = @config_presets[CONF_PRESET_CONFIG][CONF_PRESET_VERSION]
|
|
538
|
-
raise Error, 'No version found in config section.' if version.nil?
|
|
539
|
-
Log.log.debug{"conf version: #{version}"}
|
|
540
|
-
# VVV if there are any conversion needed, those happen here.
|
|
541
|
-
# Fix bug in 4.4 (creating key "true" in "default" preset)
|
|
542
|
-
@config_presets[CONF_PRESET_DEFAULTS].delete(true) if @config_presets[CONF_PRESET_DEFAULTS].is_a?(Hash)
|
|
543
|
-
# ^^^ Place new compatibility code before this line
|
|
544
|
-
# Set version to current
|
|
545
|
-
@config_presets[CONF_PRESET_CONFIG][CONF_PRESET_VERSION] = Cli::VERSION
|
|
546
|
-
unless files_to_copy.empty?
|
|
547
|
-
Log.log.warn('Copying referenced files')
|
|
548
|
-
files_to_copy.each do |file|
|
|
549
|
-
FileUtils.cp(file, @main_folder)
|
|
550
|
-
Log.log.warn{"#{file} -> #{@main_folder}"}
|
|
378
|
+
def action_documentation(location: :github, section: nil, **)
|
|
379
|
+
case location
|
|
380
|
+
when :github
|
|
381
|
+
section = "##{section}" unless section.nil?
|
|
382
|
+
Environment.instance.open_uri("#{Info::DOC_URL}#{section}")
|
|
383
|
+
return Result::Nothing.new
|
|
384
|
+
when :toc, :local
|
|
385
|
+
require 'aspera/markdown'
|
|
386
|
+
local_doc = File.join(self.class.gem_src_root, '..', 'docs', 'README.md')
|
|
387
|
+
Aspera.assert(File.exist?(local_doc), type: Cli::Error){"Local documentation not found: #{local_doc}"}
|
|
388
|
+
content = File.read(local_doc)
|
|
389
|
+
if location == :toc
|
|
390
|
+
entries = Markdown.toc(content)
|
|
391
|
+
return Result::ObjectList.new(entries, fields: %w[level title anchor])
|
|
551
392
|
end
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
rescue StandardError => e
|
|
558
|
-
Log.log.debug{"-> #{e.class.name} : #{e}"}
|
|
559
|
-
if File.exist?(@option_config_file)
|
|
560
|
-
# Then there is a problem with that file.
|
|
561
|
-
new_name = "#{@option_config_file}.pre#{Cli::VERSION}.manual_conversion_needed"
|
|
562
|
-
File.rename(@option_config_file, new_name)
|
|
563
|
-
Log.log.warn{"Renamed config file to #{new_name}."}
|
|
564
|
-
Log.log.warn('Manual Conversion is required. Next time, a new empty file will be created.')
|
|
565
|
-
end
|
|
566
|
-
raise Cli::Error, e.to_s
|
|
567
|
-
end
|
|
568
|
-
|
|
569
|
-
def install_transfer_sdk
|
|
570
|
-
asked_version = options.get_next_argument('transferd version', mandatory: false)
|
|
571
|
-
sdk_url = options.get_option(:sdk_url, mandatory: true)
|
|
572
|
-
sdk_url = nil if sdk_url.eql?(SpecialValues::DEF)
|
|
573
|
-
name, version, folder = Ascp::Installation.instance.retrieve_sdk(url: sdk_url, version: asked_version)
|
|
574
|
-
return Main.result_status("Installed #{name} version #{version} in #{folder}")
|
|
575
|
-
end
|
|
576
|
-
|
|
577
|
-
def execute_action_ascp
|
|
578
|
-
command = options.get_next_command(%i[show products info install spec schema errors])
|
|
579
|
-
case command
|
|
580
|
-
when :show
|
|
581
|
-
return Main.result_text(Ascp::Installation.instance.path(:ascp))
|
|
582
|
-
when :info
|
|
583
|
-
# Collect info from ascp executable
|
|
584
|
-
data = Ascp::Installation.instance.ascp_info
|
|
585
|
-
# Add command line transfer spec
|
|
586
|
-
data['ts'] = transfer.user_transfer_spec
|
|
587
|
-
# Add keys
|
|
588
|
-
DataRepository::ELEMENTS.each_with_object(data){ |i, h| h[i.to_s] = DataRepository.instance.item(i)}
|
|
589
|
-
# Declare those as secrets
|
|
590
|
-
SecretHider::ADDITIONAL_KEYS_TO_HIDE.concat(DataRepository::ELEMENTS.map(&:to_s))
|
|
591
|
-
return Main.result_single_object(data)
|
|
592
|
-
when :products
|
|
593
|
-
command = options.get_next_command(%i[list])
|
|
594
|
-
case command
|
|
595
|
-
when :list
|
|
596
|
-
return Main.result_object_list(Ascp::Installation.instance.installed_products, fields: %w[name app_root])
|
|
393
|
+
# :local
|
|
394
|
+
if section
|
|
395
|
+
text = Markdown.extract_section(content, section)
|
|
396
|
+
Aspera.assert(!text.nil?, type: Cli::Error){"Section not found: #{section}"}
|
|
397
|
+
return Result::Text.new(text)
|
|
597
398
|
end
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
builder = Schema::Documentation.new(TerminalFormatter, Transfer::Spec::SCHEMA, include_option: true, agent_columns: true).build
|
|
602
|
-
return Main.result_object_list(builder.rows, fields: builder.columns)
|
|
603
|
-
when :schema
|
|
604
|
-
schema = Transfer::Spec::SCHEMA.current.merge({'$comment'=>'DO NOT EDIT, this file was generated from the YAML.'})
|
|
605
|
-
agent = options.get_next_argument('transfer agent name', mandatory: false)
|
|
606
|
-
schema['properties'] = schema['properties'].select{ |_k, v| CommandLineBuilder.supported_by_agent(agent, v)} unless agent.nil?
|
|
607
|
-
schema['properties'] = schema['properties'].sort.to_h
|
|
608
|
-
return Main.result_single_object(schema)
|
|
609
|
-
when :errors
|
|
610
|
-
error_data = []
|
|
611
|
-
Ascp::Management::ERRORS.each_pair do |code, prop|
|
|
612
|
-
error_data.push(code: code, mnemonic: prop[:c], retry: prop[:r], info: prop[:a])
|
|
399
|
+
if Environment.instance.url_method.eql?(:graphical)
|
|
400
|
+
Environment.instance.open_uri("file://#{local_doc}")
|
|
401
|
+
return Result::Nothing.new
|
|
613
402
|
end
|
|
614
|
-
return
|
|
615
|
-
else Aspera.error_unexpected_value(command)
|
|
403
|
+
return Result::Text.new(content)
|
|
616
404
|
end
|
|
617
|
-
Aspera.error_unreachable_line
|
|
618
405
|
end
|
|
619
406
|
|
|
620
|
-
def
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
when :install
|
|
624
|
-
return install_transfer_sdk
|
|
625
|
-
when :list
|
|
626
|
-
sdk_list = Ascp::Installation.instance.sdk_locations
|
|
627
|
-
return Main.result_object_list(
|
|
628
|
-
sdk_list,
|
|
629
|
-
fields: sdk_list.first.keys - ['url']
|
|
630
|
-
)
|
|
631
|
-
else Aspera.error_unexpected_value(command)
|
|
632
|
-
end
|
|
633
|
-
Aspera.error_unreachable_line
|
|
407
|
+
def action_genkey(private_key_path:, private_key_length: OAuth::Jwt::DEFAULT_PRIV_KEY_LENGTH, **)
|
|
408
|
+
OAuth::Jwt.generate_rsa_private_key(path: private_key_path, length: private_key_length)
|
|
409
|
+
Result::Status.new("Generated #{private_key_length} bit RSA key: #{private_key_path}")
|
|
634
410
|
end
|
|
635
411
|
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
# require id
|
|
641
|
-
PRESET_INSTANCE_ACTIONS = %i[initialize update ask set].concat(PRESET_EXIST_ACTIONS).freeze
|
|
642
|
-
PRESET_ALL_ACTIONS = (PRESET_GBL_ACTIONS + PRESET_INSTANCE_ACTIONS).freeze
|
|
643
|
-
|
|
644
|
-
def execute_preset(action: nil, name: nil)
|
|
645
|
-
action = options.get_next_command(PRESET_ALL_ACTIONS) if action.nil?
|
|
646
|
-
name = options.instance_identifier if name.nil? && PRESET_INSTANCE_ACTIONS.include?(action)
|
|
647
|
-
name = global_default_preset if name.eql?(GLOBAL_DEFAULT_KEYWORD)
|
|
648
|
-
# Those operations require existing option
|
|
649
|
-
raise "no such preset: #{name}" if PRESET_EXIST_ACTIONS.include?(action) && !@config_presets.key?(name)
|
|
650
|
-
case action
|
|
651
|
-
when :list
|
|
652
|
-
return Main.result_value_list(@config_presets.keys, name: 'name')
|
|
653
|
-
when :overview
|
|
654
|
-
# Display process modifies the value (hide secrets): we do not want to save removed secrets
|
|
655
|
-
data = self.class.deep_clone(@config_presets)
|
|
656
|
-
formatter.hide_secrets(data)
|
|
657
|
-
result = []
|
|
658
|
-
data.each do |config, preset|
|
|
659
|
-
preset.each do |parameter, value|
|
|
660
|
-
result.push(CONF_OVERVIEW_KEYS.zip([config, parameter, value]).to_h)
|
|
661
|
-
end
|
|
662
|
-
end
|
|
663
|
-
return Main.result_object_list(result, fields: CONF_OVERVIEW_KEYS)
|
|
664
|
-
when :show
|
|
665
|
-
return Main.result_single_object(self.class.deep_clone(@config_presets[name]))
|
|
666
|
-
when :delete
|
|
667
|
-
@config_presets.delete(name)
|
|
668
|
-
return Main.result_status("Deleted: #{name}")
|
|
669
|
-
when :get
|
|
670
|
-
param_name = options.get_next_argument('parameter name')
|
|
671
|
-
value = @config_presets[name][param_name]
|
|
672
|
-
raise "no such option in preset #{name} : #{param_name}" if value.nil?
|
|
673
|
-
case value
|
|
674
|
-
when Numeric, String then return Main.result_text(ExtendedValue.instance.evaluate(value.to_s, context: 'preset'))
|
|
675
|
-
end
|
|
676
|
-
return Main.result_single_object(value)
|
|
677
|
-
when :unset
|
|
678
|
-
param_name = options.get_next_argument('parameter name')
|
|
679
|
-
@config_presets[name].delete(param_name)
|
|
680
|
-
return Main.result_status("Removed: #{name}: #{param_name}")
|
|
681
|
-
when :set
|
|
682
|
-
param_name = options.get_next_argument('parameter name')
|
|
683
|
-
param_name = Manager.option_line_to_name(param_name)
|
|
684
|
-
param_value = options.get_next_argument('parameter value', validation: nil)
|
|
685
|
-
set_preset_key(name, param_name, param_value)
|
|
686
|
-
return Main.result_nothing
|
|
687
|
-
when :initialize
|
|
688
|
-
config_value = options.get_next_argument('extended value', validation: Hash)
|
|
689
|
-
Log.log.warn{"configuration already exists: #{name}, overwriting"} if @config_presets.key?(name)
|
|
690
|
-
@config_presets[name] = config_value
|
|
691
|
-
return Main.result_status("Modified: #{@option_config_file}")
|
|
692
|
-
when :update
|
|
693
|
-
# get unprocessed options
|
|
694
|
-
unprocessed_options = options.unprocessed_options_with_value
|
|
695
|
-
Log.log.debug{"opts=#{unprocessed_options}"}
|
|
696
|
-
@config_presets[name] ||= {}
|
|
697
|
-
@config_presets[name].merge!(unprocessed_options)
|
|
698
|
-
return Main.result_status("Updated: #{name}")
|
|
699
|
-
when :ask
|
|
700
|
-
options.ask_missing_mandatory = true
|
|
701
|
-
@config_presets[name] ||= {}
|
|
702
|
-
options.get_next_argument('option names', multiple: true).each do |option_name|
|
|
703
|
-
option_value = options.get_interactive(option_name, check_option: true)
|
|
704
|
-
@config_presets[name][option_name] = option_value
|
|
705
|
-
end
|
|
706
|
-
return Main.result_status("Updated: #{name}")
|
|
707
|
-
when :lookup
|
|
708
|
-
BasicAuth.declare_options(options)
|
|
709
|
-
url = options.get_option(:url, mandatory: true)
|
|
710
|
-
user = options.get_option(:username, mandatory: true)
|
|
711
|
-
result = lookup_preset(url: url, username: user)
|
|
712
|
-
raise Error, 'no such config found' if result.nil?
|
|
713
|
-
return Main.result_single_object(result)
|
|
714
|
-
when :secure
|
|
715
|
-
identifier = options.get_next_argument('config name', mandatory: false)
|
|
716
|
-
preset_names = identifier.nil? ? @config_presets.keys : [identifier]
|
|
717
|
-
secret_keywords = %w[password secret].freeze
|
|
718
|
-
preset_names.each do |preset_name|
|
|
719
|
-
preset = @config_presets[preset_name]
|
|
720
|
-
next unless preset.is_a?(Hash)
|
|
721
|
-
preset.each_key do |option_name|
|
|
722
|
-
secret_keywords.each do |keyword|
|
|
723
|
-
next unless option_name.end_with?(keyword)
|
|
724
|
-
vault_label = preset_name
|
|
725
|
-
incr = 0
|
|
726
|
-
until vault.get(label: vault_label, exception: false).nil?
|
|
727
|
-
vault_label = "#{preset_name}#{incr}"
|
|
728
|
-
incr += 1
|
|
729
|
-
end
|
|
730
|
-
to_set = {label: vault_label, password: preset[option_name]}
|
|
731
|
-
puts "need to encode #{preset_name}.#{option_name} -> #{vault_label} -> #{to_set}"
|
|
732
|
-
# to_copy=%i[]
|
|
733
|
-
vault.set(to_set)
|
|
734
|
-
preset[option_name] = "@vault:#{vault_label}.password"
|
|
735
|
-
end
|
|
736
|
-
end
|
|
737
|
-
end
|
|
738
|
-
return Main.result_status('Secrets secured in vault: Make sure to save the vault password securely.')
|
|
739
|
-
end
|
|
412
|
+
def action_remote_certificate_chain(remote_url:, **)
|
|
413
|
+
remote_chain = Rest.remote_certificate_chain(remote_url, as_string: false)
|
|
414
|
+
Aspera.assert(remote_chain&.first){"No certificate found for #{remote_url}"}
|
|
415
|
+
Result::Text.new(remote_chain.map(&:to_pem).join("\n"))
|
|
740
416
|
end
|
|
741
417
|
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
genkey
|
|
747
|
-
pubkey
|
|
748
|
-
remote_certificate
|
|
749
|
-
gem
|
|
750
|
-
plugins
|
|
751
|
-
tokens
|
|
752
|
-
echo
|
|
753
|
-
download
|
|
754
|
-
wizard
|
|
755
|
-
detect
|
|
756
|
-
coffee
|
|
757
|
-
image
|
|
758
|
-
ascp
|
|
759
|
-
sync
|
|
760
|
-
transferd
|
|
761
|
-
email_test
|
|
762
|
-
smtp_settings
|
|
763
|
-
proxy_check
|
|
764
|
-
folder
|
|
765
|
-
file
|
|
766
|
-
check_update
|
|
767
|
-
initdemo
|
|
768
|
-
vault
|
|
769
|
-
test
|
|
770
|
-
platform
|
|
771
|
-
].freeze
|
|
772
|
-
|
|
773
|
-
# Main action procedure for plugin
|
|
774
|
-
def execute_action
|
|
775
|
-
action = options.get_next_command(ACTIONS)
|
|
776
|
-
case action
|
|
777
|
-
when :preset # Newer syntax
|
|
778
|
-
return execute_preset
|
|
779
|
-
when :open
|
|
780
|
-
Environment.instance.open_editor(@option_config_file.to_s)
|
|
781
|
-
return Main.result_nothing
|
|
782
|
-
when :documentation
|
|
783
|
-
section = options.get_next_argument('private key file path', mandatory: false)
|
|
784
|
-
section = "##{section}" unless section.nil?
|
|
785
|
-
Environment.instance.open_uri("#{Info::DOC_URL}#{section}")
|
|
786
|
-
return Main.result_nothing
|
|
787
|
-
when :genkey # Generate new rsa key
|
|
788
|
-
private_key_path = options.get_next_argument('private key file path')
|
|
789
|
-
private_key_length = options.get_next_argument('size in bits', mandatory: false, validation: Integer, default: OAuth::Jwt::DEFAULT_PRIV_KEY_LENGTH)
|
|
790
|
-
OAuth::Jwt.generate_rsa_private_key(path: private_key_path, length: private_key_length)
|
|
791
|
-
return Main.result_status("Generated #{private_key_length} bit RSA key: #{private_key_path}")
|
|
792
|
-
when :pubkey # Get pub key
|
|
793
|
-
private_key_pem = options.get_next_argument('private key PEM value')
|
|
794
|
-
return Main.result_text(OpenSSL::PKey::RSA.new(private_key_pem).public_key.to_s)
|
|
795
|
-
when :remote_certificate
|
|
796
|
-
cert_action = options.get_next_command(%i[chain only name])
|
|
797
|
-
remote_url = options.get_next_argument('remote URL')
|
|
798
|
-
remote_chain = Rest.remote_certificate_chain(remote_url, as_string: false)
|
|
799
|
-
raise "No certificate found for #{remote_url}" unless remote_chain&.first
|
|
800
|
-
case cert_action
|
|
801
|
-
when :chain
|
|
802
|
-
return Main.result_text(remote_chain.map(&:to_pem).join("\n"))
|
|
803
|
-
when :only
|
|
804
|
-
return Main.result_text(remote_chain.first.to_pem)
|
|
805
|
-
when :name
|
|
806
|
-
return Main.result_text(remote_chain.first.subject.to_a.find{ |name, _, _| name == 'CN'}[1])
|
|
807
|
-
end
|
|
808
|
-
when :echo # Display the content of a value given on command line
|
|
809
|
-
return Main.result_auto(options.get_next_argument('value', validation: nil))
|
|
810
|
-
when :download
|
|
811
|
-
file_url = options.get_next_argument('source URL').chomp
|
|
812
|
-
file_dest = options.get_next_argument('file path', mandatory: false)
|
|
813
|
-
file_dest = File.join(transfer.destination_folder(Transfer::Spec::DIRECTION_RECEIVE), file_url.gsub(%r{.*/}, '')) if file_dest.nil?
|
|
814
|
-
Log.log.info("Downloading: #{file_url}")
|
|
815
|
-
Rest.new(base_url: file_url).call(operation: 'GET', save_to_file: file_dest)
|
|
816
|
-
return Main.result_status("Saved to: #{file_dest}")
|
|
817
|
-
when :tokens
|
|
818
|
-
require 'aspera/api/node'
|
|
819
|
-
case options.get_next_command(%i{flush list show})
|
|
820
|
-
when :flush
|
|
821
|
-
return Main.result_value_list(OAuth::Factory.instance.flush_tokens, name: 'file')
|
|
822
|
-
when :list
|
|
823
|
-
return Main.result_object_list(OAuth::Factory.instance.persisted_tokens)
|
|
824
|
-
when :show
|
|
825
|
-
data = OAuth::Factory.instance.get_token_info(options.instance_identifier)
|
|
826
|
-
raise Cli::Error, 'Unknown identifier' if data.nil?
|
|
827
|
-
return Main.result_single_object(data)
|
|
828
|
-
end
|
|
829
|
-
when :plugins
|
|
830
|
-
case options.get_next_command(%i[list create])
|
|
831
|
-
when :list
|
|
832
|
-
result = []
|
|
833
|
-
Plugins::Factory.instance.plugin_list.each do |name|
|
|
834
|
-
plugin_class = Plugins::Factory.instance.plugin_class(name)
|
|
835
|
-
result.push({
|
|
836
|
-
plugin: name,
|
|
837
|
-
detect: TerminalFormatter.tick(plugin_class.respond_to?(:detect)),
|
|
838
|
-
wizard: TerminalFormatter.tick(plugin_class.method_defined?(:wizard)),
|
|
839
|
-
path: Plugins::Factory.instance.plugin_source(name)
|
|
840
|
-
})
|
|
841
|
-
end
|
|
842
|
-
return Main.result_object_list(result, fields: %w[plugin detect wizard path])
|
|
843
|
-
when :create
|
|
844
|
-
plugin_name = options.get_next_argument('name').downcase
|
|
845
|
-
destination_folder = options.get_next_argument('folder', mandatory: false) || File.join(@main_folder, ASPERA_PLUGINS_FOLDERNAME)
|
|
846
|
-
plugin_file = File.join(destination_folder, "#{plugin_name}.rb")
|
|
847
|
-
content = <<~END_OF_PLUGIN_CODE
|
|
848
|
-
require 'aspera/cli/plugins/base'
|
|
849
|
-
module Aspera
|
|
850
|
-
module Cli
|
|
851
|
-
module Plugins
|
|
852
|
-
class #{plugin_name.snake_to_capital} < Base
|
|
853
|
-
ACTIONS=[]
|
|
854
|
-
def execute_action
|
|
855
|
-
return Main.result_status('You called plugin #{plugin_name}')
|
|
856
|
-
end
|
|
857
|
-
end
|
|
858
|
-
end
|
|
859
|
-
end
|
|
860
|
-
end
|
|
861
|
-
END_OF_PLUGIN_CODE
|
|
862
|
-
File.write(plugin_file, content)
|
|
863
|
-
return Main.result_status("Created #{plugin_file}")
|
|
864
|
-
end
|
|
865
|
-
when :detect, :wizard
|
|
866
|
-
# Interactive mode
|
|
867
|
-
options.ask_missing_mandatory = true
|
|
868
|
-
# Detect plugins by url and optional query
|
|
869
|
-
apps = @wizard.identify_plugins_for_url.freeze
|
|
870
|
-
return Main.result_object_list(apps) if action.eql?(:detect)
|
|
871
|
-
return @wizard.find(apps)
|
|
872
|
-
when :coffee
|
|
873
|
-
return Main.result_image(COFFEE_IMAGE_URL)
|
|
874
|
-
when :image
|
|
875
|
-
return Main.result_image(options.get_next_argument('image URI or blob'))
|
|
876
|
-
when :ascp
|
|
877
|
-
execute_action_ascp
|
|
878
|
-
when :sync
|
|
879
|
-
case options.get_next_command(%i[spec admin translate])
|
|
880
|
-
when :spec
|
|
881
|
-
builder = Schema::Documentation.new(TerminalFormatter, Sync::Operations::CONF_SCHEMA, include_option: true).build
|
|
882
|
-
return Main.result_object_list(builder.rows, fields: builder.columns)
|
|
883
|
-
when :admin
|
|
884
|
-
return execute_sync_admin
|
|
885
|
-
when :translate
|
|
886
|
-
return Main.result_single_object(Sync::Operations.args_to_conf(options.get_next_argument('async arguments', multiple: true)))
|
|
887
|
-
else Aspera.error_unreachable_line
|
|
888
|
-
end
|
|
889
|
-
when :transferd
|
|
890
|
-
execute_action_transferd
|
|
891
|
-
when :gem
|
|
892
|
-
case options.get_next_command(%i[path version name])
|
|
893
|
-
when :path then return Main.result_text(self.class.gem_src_root)
|
|
894
|
-
when :version then return Main.result_text(Cli::VERSION)
|
|
895
|
-
when :name then return Main.result_text(Info::GEM_NAME)
|
|
896
|
-
else Aspera.error_unreachable_line
|
|
897
|
-
end
|
|
898
|
-
when :folder
|
|
899
|
-
return Main.result_text(@main_folder)
|
|
900
|
-
when :file
|
|
901
|
-
return Main.result_text(@option_config_file)
|
|
902
|
-
when :email_test
|
|
903
|
-
send_email_template(email_template_default: EMAIL_TEST_TEMPLATE)
|
|
904
|
-
return Main.result_nothing
|
|
905
|
-
when :smtp_settings
|
|
906
|
-
return Main.result_single_object(email_settings)
|
|
907
|
-
when :proxy_check
|
|
908
|
-
# Ensure fpac was provided
|
|
909
|
-
options.get_option(:fpac, mandatory: true)
|
|
910
|
-
server_url = options.get_next_argument('server url')
|
|
911
|
-
return Main.result_text(@pac_exec.get_proxies(server_url))
|
|
912
|
-
when :check_update
|
|
913
|
-
return Main.result_single_object(check_gem_version)
|
|
914
|
-
when :initdemo
|
|
915
|
-
if @config_presets.key?(DEMO_PRESET)
|
|
916
|
-
Log.log.warn{"Demo server preset already present: #{DEMO_PRESET}"}
|
|
917
|
-
else
|
|
918
|
-
Log.log.info{"Creating Demo server preset: #{DEMO_PRESET}"}
|
|
919
|
-
@config_presets[DEMO_PRESET] = {
|
|
920
|
-
'url' => "ssh://#{DEMO_SERVER}.asperasoft.com:33001",
|
|
921
|
-
'username' => ASPERA,
|
|
922
|
-
'ssAP'.downcase.reverse + 'drow'.reverse => DEMO_SERVER + ASPERA # cspell:disable-line
|
|
923
|
-
}
|
|
924
|
-
end
|
|
925
|
-
@config_presets[CONF_PRESET_DEFAULTS] ||= {}
|
|
926
|
-
if @config_presets[CONF_PRESET_DEFAULTS].key?(SERVER_COMMAND)
|
|
927
|
-
Log.log.warn{"Server default preset already set to: #{@config_presets[CONF_PRESET_DEFAULTS][SERVER_COMMAND]}"}
|
|
928
|
-
Log.log.warn{"Use #{DEMO_PRESET} for demo: -P#{DEMO_PRESET}"} unless
|
|
929
|
-
DEMO_PRESET.eql?(@config_presets[CONF_PRESET_DEFAULTS][SERVER_COMMAND])
|
|
930
|
-
else
|
|
931
|
-
@config_presets[CONF_PRESET_DEFAULTS][SERVER_COMMAND] = DEMO_PRESET
|
|
932
|
-
Log.log.info{"Setting server default preset to : #{DEMO_PRESET}"}
|
|
933
|
-
end
|
|
934
|
-
return Main.result_status('Done')
|
|
935
|
-
when :vault then execute_vault
|
|
936
|
-
when :test then return execute_test
|
|
937
|
-
when :platform
|
|
938
|
-
return Main.result_text(Environment.instance.architecture)
|
|
939
|
-
else Aspera.error_unreachable_line
|
|
940
|
-
end
|
|
418
|
+
def action_remote_certificate_only(remote_url:, **)
|
|
419
|
+
remote_chain = Rest.remote_certificate_chain(remote_url, as_string: false)
|
|
420
|
+
Aspera.assert(remote_chain&.first){"No certificate found for #{remote_url}"}
|
|
421
|
+
Result::Text.new(remote_chain.first.to_pem)
|
|
941
422
|
end
|
|
942
423
|
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
smtp = smtp.symbolize_keys
|
|
948
|
-
unsupported = smtp.keys - SMTP_CONF_PARAMS
|
|
949
|
-
raise Cli::Error, "Unsupported SMTP parameter: #{unsupported.join(', ')}, use: #{SMTP_CONF_PARAMS.join(', ')}" unless unsupported.empty?
|
|
950
|
-
# Defaults
|
|
951
|
-
# smtp[:ssl] = nil (false)
|
|
952
|
-
smtp[:tls] = !smtp[:ssl] unless smtp.key?(:tls)
|
|
953
|
-
smtp[:port] ||= if smtp[:tls]
|
|
954
|
-
587
|
|
955
|
-
elsif smtp[:ssl]
|
|
956
|
-
465
|
|
957
|
-
else
|
|
958
|
-
25
|
|
959
|
-
end
|
|
960
|
-
smtp[:from_email] ||= smtp[:username] if smtp.key?(:username)
|
|
961
|
-
smtp[:from_name] ||= smtp[:from_email].sub(/@.*$/, '').gsub(/[^a-zA-Z]/, ' ').capitalize if smtp.key?(:username)
|
|
962
|
-
smtp[:domain] ||= smtp[:from_email].sub(/^.*@/, '') if smtp.key?(:from_email)
|
|
963
|
-
# Check minimum required
|
|
964
|
-
%i[server port domain].each do |n|
|
|
965
|
-
Aspera.assert(smtp.key?(n)){"Missing mandatory smtp parameter: #{n}"}
|
|
966
|
-
end
|
|
967
|
-
Log.log.debug{"smtp=#{smtp}"}
|
|
968
|
-
return smtp
|
|
424
|
+
def action_remote_certificate_name(remote_url:, **)
|
|
425
|
+
remote_chain = Rest.remote_certificate_chain(remote_url, as_string: false)
|
|
426
|
+
Aspera.assert(remote_chain&.first){"No certificate found for #{remote_url}"}
|
|
427
|
+
Result::Text.new(remote_chain.first.subject.to_a.find{ |name, _, _| name == 'CN'}[1])
|
|
969
428
|
end
|
|
970
429
|
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
mail_conf = email_settings
|
|
978
|
-
values[:from_name] ||= mail_conf[:from_name]
|
|
979
|
-
values[:from_email] ||= mail_conf[:from_email]
|
|
980
|
-
%i[to from_email].each do |n|
|
|
981
|
-
Aspera.assert_type(values[n], String){"Missing email parameter: #{n} in config"}
|
|
982
|
-
end
|
|
983
|
-
start_options = [mail_conf[:domain]]
|
|
984
|
-
start_options.push(mail_conf[:username], mail_conf[:password], :login) if mail_conf.key?(:username) && mail_conf.key?(:password)
|
|
985
|
-
# Create a binding with only variables defined in values
|
|
986
|
-
template_binding = Environment.empty_binding
|
|
987
|
-
# Add variables to binding
|
|
988
|
-
values.each do |k, v|
|
|
989
|
-
Aspera.assert_type(k, Symbol)
|
|
990
|
-
template_binding.local_variable_set(k, v)
|
|
991
|
-
end
|
|
992
|
-
# Execute template
|
|
993
|
-
msg_with_headers = ERB.new(notify_template).result(template_binding)
|
|
994
|
-
Log.dump(:msg_with_headers, msg_with_headers)
|
|
995
|
-
require 'net/smtp'
|
|
996
|
-
smtp = Net::SMTP.new(mail_conf[:server], mail_conf[:port])
|
|
997
|
-
smtp.enable_starttls if mail_conf[:tls]
|
|
998
|
-
smtp.enable_tls if mail_conf[:ssl]
|
|
999
|
-
smtp.start(*start_options) do |smtp_session|
|
|
1000
|
-
smtp_session.send_message(msg_with_headers, values[:from_email], values[:to])
|
|
1001
|
-
end
|
|
1002
|
-
nil
|
|
430
|
+
def action_download(file_url:, file_dest: nil, **)
|
|
431
|
+
file_url = file_url.chomp
|
|
432
|
+
file_dest = File.join(transfer.destination_folder(Transfer::Spec::DIRECTION_RECEIVE), file_url.gsub(%r{.*/}, '')) if file_dest.nil?
|
|
433
|
+
Log.log.info("Downloading: #{file_url}")
|
|
434
|
+
Rest.new(base_url: file_url).call(operation: 'GET', save_to_file: file_dest)
|
|
435
|
+
Result::Status.new("Saved to: #{file_dest}")
|
|
1003
436
|
end
|
|
1004
437
|
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
return false if @config_checksum_on_disk.eql?(current_checksum)
|
|
1011
|
-
FileUtils.mkdir_p(@main_folder)
|
|
1012
|
-
Environment.restrict_file_access(@main_folder)
|
|
1013
|
-
Log.log.info{"Saving config file: #{@option_config_file}"}
|
|
1014
|
-
Environment.write_file_restricted(@option_config_file, force: true){@config_presets.to_yaml}
|
|
1015
|
-
@config_checksum_on_disk = current_checksum
|
|
1016
|
-
return true
|
|
438
|
+
def action_tokens_show(token_id:, **)
|
|
439
|
+
require 'aspera/api/node'
|
|
440
|
+
data = OAuth::Factory.instance.get_token_info(token_id)
|
|
441
|
+
Aspera.assert(!data.nil?, type: Cli::Error){'Unknown identifier'}
|
|
442
|
+
Result::SingleObject.new(data)
|
|
1017
443
|
end
|
|
1018
444
|
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
Log.log.debug('No default section')
|
|
1029
|
-
return
|
|
1030
|
-
end
|
|
1031
|
-
Aspera.assert_type(@config_presets[CONF_PRESET_DEFAULTS], Hash){'default section'}
|
|
1032
|
-
if !@config_presets[CONF_PRESET_DEFAULTS].key?(plugin_name_sym.to_s)
|
|
1033
|
-
Log.log.debug("No default for #{plugin_name_sym}")
|
|
1034
|
-
return
|
|
445
|
+
def action_plugins_list
|
|
446
|
+
result = Plugins::Factory.instance.plugin_list.map do |name|
|
|
447
|
+
plugin_class = Plugins::Factory.instance.plugin_class(name)
|
|
448
|
+
{
|
|
449
|
+
plugin: name,
|
|
450
|
+
detect: TerminalFormatter.tick(plugin_class.respond_to?(:detect)),
|
|
451
|
+
wizard: TerminalFormatter.tick(plugin_class.method_defined?(:wizard)),
|
|
452
|
+
path: Plugins::Factory.instance.plugin_source(name)
|
|
453
|
+
}
|
|
1035
454
|
end
|
|
1036
|
-
|
|
1037
|
-
if !@config_presets.key?(default_config_name)
|
|
1038
|
-
Log.log.error do
|
|
1039
|
-
"Default config name [#{default_config_name}] specified for plugin [#{plugin_name_sym}], but it does not exist in config file.\n" \
|
|
1040
|
-
"Please fix the issue: either create preset with one parameter:\n" \
|
|
1041
|
-
"#{Info::CMD_NAME} config id #{default_config_name} init @json:'{}'\n" \
|
|
1042
|
-
"or remove default:\n#{Info::CMD_NAME} config id default remove #{plugin_name_sym}"
|
|
1043
|
-
end
|
|
1044
|
-
raise Cli::Error, "No such preset: #{default_config_name}"
|
|
1045
|
-
end
|
|
1046
|
-
Aspera.assert_type(@config_presets[default_config_name], Hash, type: Cli::Error){'preset type'}
|
|
1047
|
-
return default_config_name
|
|
455
|
+
Result::ObjectList.new(result, fields: %w[plugin detect wizard path])
|
|
1048
456
|
end
|
|
1049
457
|
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
return Main.result_status('Vault password updated')
|
|
1072
|
-
end
|
|
458
|
+
def action_plugins_create(name:, folder: nil, **)
|
|
459
|
+
plugin_name = name.downcase
|
|
460
|
+
destination_folder = folder || File.join(context.main_folder, ASPERA_PLUGINS_FOLDERNAME)
|
|
461
|
+
plugin_file = File.join(destination_folder, "#{plugin_name}.rb")
|
|
462
|
+
content = <<~END_OF_PLUGIN_CODE
|
|
463
|
+
require 'aspera/cli/plugins/base'
|
|
464
|
+
module Aspera
|
|
465
|
+
module Cli
|
|
466
|
+
module Plugins
|
|
467
|
+
class #{plugin_name.snake_to_capital} < Base
|
|
468
|
+
command :example, description: 'example command', action: :action_example
|
|
469
|
+
def action_example
|
|
470
|
+
Result::Status.new('You called plugin #{plugin_name}')
|
|
471
|
+
end
|
|
472
|
+
end
|
|
473
|
+
end
|
|
474
|
+
end
|
|
475
|
+
end
|
|
476
|
+
END_OF_PLUGIN_CODE
|
|
477
|
+
File.write(plugin_file, content)
|
|
478
|
+
Result::Status.new("Created #{plugin_file}")
|
|
1073
479
|
end
|
|
1074
480
|
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
# This raise exception if label not found:
|
|
1080
|
-
info = vault.get(label: m[0])
|
|
1081
|
-
value = info[m[1].to_sym]
|
|
1082
|
-
raise "no such entry value: #{m[1]}" if value.nil?
|
|
1083
|
-
return value
|
|
481
|
+
def action_detect(url:, plugin_name: nil, **)
|
|
482
|
+
options.ask_missing_mandatory = true
|
|
483
|
+
apps = @wizard.identify_plugins_for_url(url: url, plugin_name: plugin_name).freeze
|
|
484
|
+
Result::ObjectList.new(apps)
|
|
1084
485
|
end
|
|
1085
486
|
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
info[:type] ||= 'file'
|
|
1091
|
-
require 'aspera/keychain/factory'
|
|
1092
|
-
@vault_instance = Keychain::Factory.create(
|
|
1093
|
-
info,
|
|
1094
|
-
Info::CMD_NAME,
|
|
1095
|
-
@main_folder,
|
|
1096
|
-
options.get_option(:vault_password)
|
|
1097
|
-
)
|
|
487
|
+
def action_wizard(url:, plugin_name: nil, preset_name: '', **)
|
|
488
|
+
options.ask_missing_mandatory = true
|
|
489
|
+
apps = @wizard.identify_plugins_for_url(url: url, plugin_name: plugin_name).freeze
|
|
490
|
+
@wizard.find(apps, preset_name: preset_name)
|
|
1098
491
|
end
|
|
1099
492
|
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
#
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
493
|
+
def action_initdemo
|
|
494
|
+
cp = presets.config_presets
|
|
495
|
+
if cp.key?(DEMO_PRESET)
|
|
496
|
+
Log.log.warn{"Demo server preset already present: #{DEMO_PRESET}"}
|
|
497
|
+
else
|
|
498
|
+
Log.log.info{"Creating Demo server preset: #{DEMO_PRESET}"}
|
|
499
|
+
cp[DEMO_PRESET] = {
|
|
500
|
+
'url' => "ssh://#{DEMO_SERVER}.asperasoft.com:33001",
|
|
501
|
+
'username' => ASPERA,
|
|
502
|
+
'ssAP'.downcase.reverse + 'drow'.reverse => DEMO_SERVER + ASPERA # cspell:disable-line
|
|
503
|
+
}
|
|
504
|
+
end
|
|
505
|
+
cp[PresetManager::Key::DEFAULTS] ||= {}
|
|
506
|
+
if cp[PresetManager::Key::DEFAULTS].key?(SERVER_COMMAND)
|
|
507
|
+
Log.log.warn{"Server default preset already set to: #{cp[PresetManager::Key::DEFAULTS][SERVER_COMMAND]}"}
|
|
508
|
+
Log.log.warn{"Use #{DEMO_PRESET} for demo: -P#{DEMO_PRESET}"} unless
|
|
509
|
+
DEMO_PRESET.eql?(cp[PresetManager::Key::DEFAULTS][SERVER_COMMAND])
|
|
510
|
+
else
|
|
511
|
+
cp[PresetManager::Key::DEFAULTS][SERVER_COMMAND] = DEMO_PRESET
|
|
512
|
+
Log.log.info{"Setting server default preset to : #{DEMO_PRESET}"}
|
|
513
|
+
end
|
|
514
|
+
Result::Status.new('Done')
|
|
515
|
+
end
|
|
516
|
+
|
|
517
|
+
def action_commands
|
|
518
|
+
commands = Plugins::Factory.instance.plugin_list.flat_map do |name|
|
|
519
|
+
plugin_class = Plugins::Factory.instance.plugin_class(name)
|
|
520
|
+
reg = plugin_class.command_registry
|
|
521
|
+
reg.all_paths.reject{ |path| reg.children_of(path).any?}.map do |path|
|
|
522
|
+
spec = reg[path]
|
|
523
|
+
arg_tokens = spec&.arguments.to_a.map do |a|
|
|
524
|
+
token =
|
|
525
|
+
if a.allowed
|
|
526
|
+
a.allowed.join('|')
|
|
527
|
+
else
|
|
528
|
+
a.name.to_s
|
|
529
|
+
end
|
|
530
|
+
token += '...' if a.multiple
|
|
531
|
+
a.mandatory ? "<#{token}>" : "[<#{token}>]"
|
|
532
|
+
end
|
|
533
|
+
syntax = ([name.to_s] + path.map(&:to_s) + arg_tokens).join(' ')
|
|
534
|
+
{
|
|
535
|
+
syntax: syntax,
|
|
536
|
+
description: spec&.description.to_s
|
|
537
|
+
}
|
|
538
|
+
end
|
|
1112
539
|
end
|
|
540
|
+
Result::ObjectList.new(commands, fields: %w[syntax description])
|
|
1113
541
|
end
|
|
1114
542
|
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
543
|
+
def action_options(plugin_name:, **)
|
|
544
|
+
# Instantiate the plugin so that it registers all its options in context.options
|
|
545
|
+
Plugins::Factory.instance.create(plugin_name.to_sym, context: context)
|
|
546
|
+
rows = context.options.declared_options.map do |sym, opt|
|
|
547
|
+
row = {
|
|
548
|
+
option: "--#{sym.to_s.tr('_', '-')}",
|
|
549
|
+
description: opt.description.to_s
|
|
550
|
+
}
|
|
551
|
+
row[:allowed] = opt.values.join('|') if opt.values&.any?
|
|
552
|
+
row[:deprecated] = opt.deprecation if opt.deprecation
|
|
553
|
+
row
|
|
554
|
+
end
|
|
555
|
+
Result::ObjectList.new(rows, fields: %w[option description allowed deprecated])
|
|
1118
556
|
end
|
|
1119
557
|
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
url = canonical_url(url)
|
|
1125
|
-
Log.log.debug{"Lookup preset for #{username}@#{url}"}
|
|
1126
|
-
@config_presets.each_value do |v|
|
|
1127
|
-
next unless v.is_a?(Hash)
|
|
1128
|
-
conf_url = v['url'].is_a?(String) ? canonical_url(v['url']) : nil
|
|
1129
|
-
return self.class.deep_clone(v) if conf_url.eql?(url) && v['username'].eql?(username)
|
|
1130
|
-
end
|
|
1131
|
-
nil
|
|
558
|
+
def action_test_throw(exception_class_name:, exception_text:, **)
|
|
559
|
+
type = Object.const_get(exception_class_name)
|
|
560
|
+
Aspera.assert(type <= Exception){"#{type} is not an exception: #{type.class}"}
|
|
561
|
+
raise type, exception_text
|
|
1132
562
|
end
|
|
1133
563
|
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
secret = conf['password']
|
|
564
|
+
def action_completion_bash(words: nil, **)
|
|
565
|
+
if words.nil? || words.empty?
|
|
566
|
+
# Level 0: propose plugin names
|
|
567
|
+
Plugins::Factory.instance.plugin_list.each{ |p| puts p}
|
|
568
|
+
else
|
|
569
|
+
plugin_sym = words.first.to_sym
|
|
570
|
+
plugin_class = begin
|
|
571
|
+
Plugins::Factory.instance.plugin_class(plugin_sym)
|
|
572
|
+
rescue StandardError
|
|
573
|
+
Process.exit(0)
|
|
1145
574
|
end
|
|
575
|
+
# Navigate into the registry using remaining words as path
|
|
576
|
+
path = words[1..].map(&:to_sym)
|
|
577
|
+
plugin_class.command_registry.children_of(path).each_key{ |k| puts k}
|
|
1146
578
|
end
|
|
1147
|
-
|
|
579
|
+
Process.exit(0)
|
|
1148
580
|
end
|
|
1149
|
-
|
|
1150
|
-
# Folder in $HOME for application files (
|
|
581
|
+
|
|
582
|
+
# Folder in $HOME for application files (~/.aspera)
|
|
1151
583
|
ASPERA_HOME_FOLDER_NAME = '.aspera'
|
|
1152
|
-
# Default config file
|
|
584
|
+
# Default config file name
|
|
1153
585
|
DEFAULT_CONFIG_FILENAME = 'config.yaml'
|
|
1154
|
-
# Reserved preset names
|
|
1155
|
-
CONF_PRESET_CONFIG = 'config'
|
|
1156
|
-
CONF_PRESET_VERSION = 'version'
|
|
1157
|
-
CONF_PRESET_DEFAULTS = 'default'
|
|
1158
|
-
CONF_PRESET_GLOBAL = 'global_common_defaults'
|
|
1159
|
-
# Special name to identify value of default
|
|
1160
|
-
GLOBAL_DEFAULT_KEYWORD = 'GLOBAL'
|
|
1161
586
|
CONF_GLOBAL_SYM = :config
|
|
1162
587
|
# Folder containing custom plugins in user's config folder
|
|
1163
588
|
ASPERA_PLUGINS_FOLDERNAME = 'plugins'
|
|
@@ -1166,7 +591,6 @@ module Aspera
|
|
|
1166
591
|
REST_EXCEPTIONS_LOG_FILENAME = 'rest_exceptions.log'
|
|
1167
592
|
ASPERA = 'aspera'
|
|
1168
593
|
SERVER_COMMAND = 'server'
|
|
1169
|
-
TRANSFERD_APP_NAME = 'sdk'
|
|
1170
594
|
DEMO_SERVER = 'demo'
|
|
1171
595
|
DEMO_PRESET = 'demoserver' # cspell: disable-line
|
|
1172
596
|
EMAIL_TEST_TEMPLATE = <<~END_OF_TEMPLATE
|
|
@@ -1179,44 +603,22 @@ module Aspera
|
|
|
1179
603
|
# Special extended values
|
|
1180
604
|
EXTEND_PRESET = :preset
|
|
1181
605
|
EXTEND_VAULT = :vault
|
|
1182
|
-
EXTEND_ARGS = :''
|
|
1183
|
-
PRESET_DIG_SEPARATOR = '.'
|
|
1184
|
-
DEFAULT_CHECK_NEW_VERSION_DAYS = 7
|
|
1185
606
|
COFFEE_IMAGE_URL = 'https://enjoyjava.com/wp-content/uploads/2018/01/How-to-make-strong-coffee.jpg'
|
|
1186
|
-
GEM_CHECK_DATE_FMT = '%Y/%m/%d'
|
|
1187
|
-
# For testing only
|
|
1188
|
-
SELF_SIGNED_CERT = OpenSSL::SSL.const_get(:enon_yfirev.to_s.upcase.reverse) # cspell: disable-line
|
|
1189
|
-
CONF_OVERVIEW_KEYS = %w[preset parameter value].freeze
|
|
1190
|
-
SMTP_CONF_PARAMS = %i[server tls ssl port domain username password from_name from_email].freeze
|
|
1191
|
-
CERT_EXT = %w[crt cer pem der].freeze
|
|
1192
607
|
private_constant :ASPERA_HOME_FOLDER_NAME,
|
|
1193
608
|
:DEFAULT_CONFIG_FILENAME,
|
|
1194
|
-
:
|
|
1195
|
-
:CONF_PRESET_VERSION,
|
|
1196
|
-
:CONF_PRESET_DEFAULTS,
|
|
1197
|
-
:CONF_PRESET_GLOBAL,
|
|
609
|
+
:CONF_GLOBAL_SYM,
|
|
1198
610
|
:ASPERA_PLUGINS_FOLDERNAME,
|
|
611
|
+
:PERSISTENCY_FOLDER,
|
|
1199
612
|
:FILE_LIST_FOLDER_NAME,
|
|
1200
613
|
:REST_EXCEPTIONS_LOG_FILENAME,
|
|
1201
614
|
:ASPERA,
|
|
615
|
+
:SERVER_COMMAND,
|
|
1202
616
|
:DEMO_SERVER,
|
|
1203
617
|
:DEMO_PRESET,
|
|
1204
618
|
:EMAIL_TEST_TEMPLATE,
|
|
1205
619
|
:EXTEND_PRESET,
|
|
1206
620
|
:EXTEND_VAULT,
|
|
1207
|
-
:
|
|
1208
|
-
:SERVER_COMMAND,
|
|
1209
|
-
:PRESET_DIG_SEPARATOR,
|
|
1210
|
-
:COFFEE_IMAGE_URL,
|
|
1211
|
-
:SELF_SIGNED_CERT,
|
|
1212
|
-
:PERSISTENCY_FOLDER,
|
|
1213
|
-
:CONF_OVERVIEW_KEYS,
|
|
1214
|
-
:SMTP_CONF_PARAMS,
|
|
1215
|
-
:TRANSFERD_APP_NAME,
|
|
1216
|
-
:GLOBAL_DEFAULT_KEYWORD,
|
|
1217
|
-
:CONF_GLOBAL_SYM,
|
|
1218
|
-
:GEM_CHECK_DATE_FMT,
|
|
1219
|
-
:CERT_EXT
|
|
621
|
+
:COFFEE_IMAGE_URL
|
|
1220
622
|
end
|
|
1221
623
|
end
|
|
1222
624
|
end
|