aspera-cli 4.26.2 → 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.
Files changed (136) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +281 -21
  4. data/CONTRIBUTING.md +98 -7
  5. data/bin/ascli +3 -3
  6. data/docs/README.md +10757 -0
  7. data/docs/test-mcp-with-ai.md +202 -0
  8. data/lib/aspera/agent/base.rb +20 -6
  9. data/lib/aspera/agent/connect.rb +53 -16
  10. data/lib/aspera/agent/desktop.rb +52 -17
  11. data/lib/aspera/agent/direct.rb +26 -24
  12. data/lib/aspera/agent/httpgw.rb +1 -1
  13. data/lib/aspera/agent/node.rb +32 -0
  14. data/lib/aspera/agent/transferd.rb +39 -3
  15. data/lib/aspera/api/alee.rb +1 -1
  16. data/lib/aspera/api/aoc.rb +14 -16
  17. data/lib/aspera/api/cos_node.rb +1 -1
  18. data/lib/aspera/api/faspex.rb +15 -14
  19. data/lib/aspera/api/httpgw.rb +4 -4
  20. data/lib/aspera/api/node.rb +26 -23
  21. data/lib/aspera/api/queries/bss_subscription_account.graphql +18 -0
  22. data/lib/aspera/api/queries/bss_subscription_usage.graphql +18 -0
  23. data/lib/aspera/ascmd.rb +21 -19
  24. data/lib/aspera/ascp/installation.rb +37 -38
  25. data/lib/aspera/ascp/management.rb +2 -3
  26. data/lib/aspera/cli/ascp_actions.rb +155 -0
  27. data/lib/aspera/cli/async_transfer_store.rb +81 -0
  28. data/lib/aspera/cli/bootstrapper.rb +197 -0
  29. data/lib/aspera/cli/command_registry.rb +145 -0
  30. data/lib/aspera/cli/command_spec.rb +138 -0
  31. data/lib/aspera/cli/context.rb +16 -3
  32. data/lib/aspera/cli/error.rb +16 -0
  33. data/lib/aspera/cli/extended_value.rb +2 -1
  34. data/lib/aspera/cli/formatter.rb +41 -27
  35. data/lib/aspera/cli/gem_checker.rb +65 -0
  36. data/lib/aspera/cli/hints.rb +2 -2
  37. data/lib/aspera/cli/http.rb +70 -19
  38. data/lib/aspera/cli/info.rb +2 -0
  39. data/lib/aspera/cli/mailer.rb +97 -0
  40. data/lib/aspera/cli/mcp_tool.rb +198 -0
  41. data/lib/aspera/cli/options.schema.yaml +364 -7
  42. data/lib/aspera/cli/{manager.rb → parser.rb} +334 -142
  43. data/lib/aspera/cli/plugins/alee.rb +20 -22
  44. data/lib/aspera/cli/plugins/aoc.rb +1166 -853
  45. data/lib/aspera/cli/plugins/ats.rb +200 -161
  46. data/lib/aspera/cli/plugins/base.rb +457 -100
  47. data/lib/aspera/cli/plugins/basic_auth.rb +14 -4
  48. data/lib/aspera/cli/plugins/config.rb +428 -754
  49. data/lib/aspera/cli/plugins/console.rb +106 -64
  50. data/lib/aspera/cli/plugins/cos.rb +44 -32
  51. data/lib/aspera/cli/plugins/factory.rb +4 -4
  52. data/lib/aspera/cli/plugins/faspex.rb +294 -257
  53. data/lib/aspera/cli/plugins/faspex5.rb +585 -357
  54. data/lib/aspera/cli/plugins/faspio.rb +49 -51
  55. data/lib/aspera/cli/plugins/httpgw.rb +18 -25
  56. data/lib/aspera/cli/plugins/mcp.rb +279 -0
  57. data/lib/aspera/cli/plugins/node.rb +998 -794
  58. data/lib/aspera/cli/plugins/oauth.rb +7 -10
  59. data/lib/aspera/cli/plugins/orchestrator.rb +111 -134
  60. data/lib/aspera/cli/plugins/preview.rb +180 -139
  61. data/lib/aspera/cli/plugins/server.rb +124 -79
  62. data/lib/aspera/cli/plugins/shares.rb +301 -107
  63. data/lib/aspera/cli/preset_actions.rb +139 -0
  64. data/lib/aspera/cli/preset_manager.rb +13 -12
  65. data/lib/aspera/cli/result.rb +61 -11
  66. data/lib/aspera/cli/runner.rb +275 -141
  67. data/lib/aspera/cli/secret_finder.rb +40 -0
  68. data/lib/aspera/cli/special_values.rb +1 -0
  69. data/lib/aspera/cli/sync_actions.rb +83 -59
  70. data/lib/aspera/cli/terminal_formatter.rb +1 -1
  71. data/lib/aspera/cli/transfer_actions.rb +83 -0
  72. data/lib/aspera/cli/transfer_agent.rb +116 -51
  73. data/lib/aspera/cli/transfer_progress.rb +3 -3
  74. data/lib/aspera/cli/vault_manager.rb +57 -0
  75. data/lib/aspera/cli/version.rb +1 -1
  76. data/lib/aspera/cli/wizard.rb +21 -20
  77. data/lib/aspera/command_line_builder.rb +22 -22
  78. data/lib/aspera/data_repository.rb +2 -1
  79. data/lib/aspera/dot_container.rb +4 -4
  80. data/lib/aspera/environment.rb +18 -11
  81. data/lib/aspera/exec_spec.rb +13 -0
  82. data/lib/aspera/faspex_gw.rb +1 -1
  83. data/lib/aspera/faspex_postproc.rb +4 -3
  84. data/lib/aspera/graphql.rb +35 -0
  85. data/lib/aspera/hash_ext.rb +6 -0
  86. data/lib/aspera/json_rpc/client.rb +62 -0
  87. data/lib/aspera/json_rpc/version.rb +7 -0
  88. data/lib/aspera/keychain/encrypted_hash.rb +1 -1
  89. data/lib/aspera/keychain/factory.rb +3 -3
  90. data/lib/aspera/keychain/macos_security.rb +3 -3
  91. data/lib/aspera/link_header.rb +82 -0
  92. data/lib/aspera/log.rb +17 -3
  93. data/lib/aspera/markdown.rb +85 -2
  94. data/lib/aspera/node_simulator.rb +3 -1
  95. data/lib/aspera/oauth/base.rb +5 -5
  96. data/lib/aspera/oauth/boot.rb +2 -2
  97. data/lib/aspera/oauth/factory.rb +7 -5
  98. data/lib/aspera/oauth/jwt.rb +4 -4
  99. data/lib/aspera/oauth/web.rb +2 -2
  100. data/lib/aspera/persistency_action_once.rb +8 -8
  101. data/lib/aspera/persistency_folder.rb +10 -3
  102. data/lib/aspera/preview/file_types.rb +3 -3
  103. data/lib/aspera/preview/generator.rb +3 -3
  104. data/lib/aspera/preview/utils.rb +1 -1
  105. data/lib/aspera/products/connect.rb +1 -1
  106. data/lib/aspera/products/other.rb +1 -1
  107. data/lib/aspera/products/transferd.rb +1 -1
  108. data/lib/aspera/proxy_auto_config.rb +7 -6
  109. data/lib/aspera/rest.rb +10 -36
  110. data/lib/aspera/rest_error_analyzer.rb +3 -3
  111. data/lib/aspera/rest_errors_aspera.rb +0 -10
  112. data/lib/aspera/rest_list.rb +3 -3
  113. data/lib/aspera/schema/IBM Aspera on Cloud API-0.2.6-enhanced.yaml +1853 -510
  114. data/lib/aspera/schema/documentation.rb +57 -26
  115. data/lib/aspera/schema/reader.rb +59 -10
  116. data/lib/aspera/schema/registry.rb +39 -6
  117. data/lib/aspera/secret_hider.rb +7 -2
  118. data/lib/aspera/ssl.rb +4 -4
  119. data/lib/aspera/sync/conf.schema.yaml +2 -2
  120. data/lib/aspera/sync/operations.rb +10 -13
  121. data/lib/aspera/temp_file_manager.rb +1 -1
  122. data/lib/aspera/timer_limiter.rb +1 -1
  123. data/lib/aspera/transfer/faux_file.rb +24 -11
  124. data/lib/aspera/transfer/parameters.rb +23 -21
  125. data/lib/aspera/transfer/result.rb +74 -0
  126. data/lib/aspera/transfer/resumer.rb +6 -6
  127. data/lib/aspera/transfer/spec.rb +18 -1
  128. data/lib/aspera/transfer/spec.schema.yaml +12 -3
  129. data/lib/aspera/uri_reader.rb +2 -2
  130. data/lib/aspera/web_auth.rb +2 -2
  131. data/lib/aspera/web_server_simple.rb +8 -5
  132. data/lib/aspera/yaml.rb +2 -1
  133. data.tar.gz.sig +0 -0
  134. metadata +25 -3
  135. metadata.gz.sig +0 -0
  136. data/lib/aspera/json_rpc.rb +0 -52
@@ -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/preset_manager'
15
- require 'aspera/cli/http'
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,7 +65,7 @@ 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
71
  Aspera.assert(Dir.exist?(user_home_folder), type: Cli::Error){"Home folder does not exist: #{user_home_folder}. Check your user environment."}
@@ -80,257 +80,275 @@ 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: 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
+
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
- @main_folder = nil
91
- @option_config_file = nil
92
- @progress_bar = nil
93
- # Option to set main folder
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: 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)
130
106
  options.parse_options!
131
107
  set_sdk_dir
132
- options.declare(:locations_url, 'Ascp: URL to get download locations of Aspera Transfer Daemon', handler: {o: Ascp::Installation.instance, m: :transferd_urls})
133
- options.declare(:sdk_folder, 'Ascp: Path to folder with ascp (or product with "product:")', handler: {o: Products::Transferd, m: :sdk_directory})
134
- options.declare(:progress_bar, 'Display progress bar', allowed: Allowed::TYPES_BOOLEAN, default: Environment.terminal?)
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
- def setup_pac_executor
166
- pac_script = options.get_option(:fpac)
167
- return unless pac_script
168
-
169
- @pac_exec = ProxyAutoConfig.new(pac_script).register_uri_generic
170
- proxy_user_pass = options.get_option(:proxy_credentials)
171
- if proxy_user_pass
172
- Aspera.assert(proxy_user_pass.length.eql?(2), type: Cli::BadArgument){"proxy_credentials shall have two elements (#{proxy_user_pass.length})"}
173
- @pac_exec.proxy_user = proxy_user_pass[0]
174
- @pac_exec.proxy_pass = proxy_user_pass[1]
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
175
148
  end
176
- end
177
-
178
- def setup_rest_and_transfer_runtime
179
- RestParameters.instance.user_agent = Info::CMD_NAME
180
- RestParameters.instance.progress_bar = @progress_bar
181
- RestParameters.instance.session_cb = ->(http_session){ context.http_config.update_session(http_session)}
182
- RestParameters.instance.spinner_cb = ->(title = nil, action: :spin){formatter.long_operation(title, action: action)}
183
- # Promote http_options keys that target global singletons (RestParameters, SSL, OAuth)
184
- http_opts = context.http_config.http_options
185
- keys_to_delete = []
186
- http_opts.each do |k, v|
187
- method = "#{k}=".to_sym
188
- if RestParameters.instance.respond_to?(method)
189
- keys_to_delete.push(k)
190
- RestParameters.instance.send(method, v)
191
- elsif k.eql?('ssl_options')
192
- keys_to_delete.push(k)
193
- Aspera::SSL.option_list = v
194
- elsif OAuth::Factory.instance.parameters.key?(k.to_sym)
195
- keys_to_delete.push(k)
196
- OAuth::Factory.instance.parameters[k.to_sym] = v
197
- 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
198
192
  end
199
- keys_to_delete.each{ |k| http_opts.delete(k)}
200
- OAuth::Factory.instance.persist_mgr = persistency if @option_cache_tokens
201
- OAuth::Web.additional_info = "#{Info::CMD_NAME} v#{Cli::VERSION}"
202
- Transfer::Parameters.file_list_folder = File.join(@main_folder, FILE_LIST_FOLDER_NAME)
203
- RestErrorAnalyzer.instance.log_file = File.join(@main_folder, REST_EXCEPTIONS_LOG_FILENAME)
204
- # Register aspera REST call error handlers
205
- RestErrorsAspera.register_handlers
206
- end
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
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')
243
201
  end
244
- end
245
-
246
-
247
- def check_gem_version
248
- latest_version =
249
- begin
250
- Rest.new(base_url: 'https://rubygems.org/api/v1').read("versions/#{Info::GEM_NAME}/latest.json")['version']
251
- rescue StandardError
252
- Log.log.warn('Could not retrieve latest gem version on rubygems.')
253
- '0'
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')
254
244
  end
255
- if Gem::Version.new(Environment.ruby_version) < Gem::Version.new(Info::RUBY_FUTURE_MINIMUM_VERSION)
256
- Log.log.warn do
257
- "Note that a future version will require Ruby version #{Info::RUBY_FUTURE_MINIMUM_VERSION} at minimum, " \
258
- "you are using #{Environment.ruby_version}"
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)
259
251
  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
252
  )
281
- # Get persisted date or nil
282
- current_date = Date.today
283
- last_check_days = (current_date - Date.strptime(last_check_array.first, GEM_CHECK_DATE_FMT)) rescue nil
284
- Log.log.debug{"gem check new version: #{delay_days}, #{last_check_days}, #{current_date}, #{last_check_array}"}
285
- return if !last_check_days.nil? && last_check_days < delay_days
286
- # Generate timestamp
287
- last_check_array[0] = current_date.strftime(GEM_CHECK_DATE_FMT)
288
- check_date_persist.save
289
- # Compare this version and the one on internet
290
- check_data = check_gem_version
291
- Log.log.warn do
292
- "A new version is available: #{check_data[:latest]}. You have #{check_data[:current]}. Upgrade with: gem update #{check_data[:name]}"
293
- end if check_data[:need_update]
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
294
282
  end
295
283
 
296
- # Delegation to PresetManager: loads default preset options for a plugin
297
- def add_plugin_default_preset(plugin_name_sym)
298
- default_config_name = presets.plugin_default_name(plugin_name_sym)
299
- Log.log.debug{"add_plugin_default_preset:#{plugin_name_sym}:#{default_config_name}"}
300
- options.add_option_preset(presets.by_name(default_config_name), 'default_plugin', override: false) unless default_config_name.nil?
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}]
301
292
  end
302
293
 
303
- # Delegations to PresetManager kept for backward compatibility
304
- def defaults_set(plugin_name, preset_name, preset_values, option_default, option_override)
305
- presets.defaults_set(plugin_name, preset_name, preset_values, option_default, option_override)
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'
306
301
  end
307
302
 
308
- def set_preset_key(preset, param_name, param_value)
309
- presets.set_key(preset, param_name, param_value)
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'
310
308
  end
311
309
 
312
- def set_global_default(key, value)
313
- presets.set_global_default(key, value)
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)
317
+ end
318
+ )
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))}
314
324
  end
315
325
 
316
- def preset_by_name(config_name, include_path = [])
317
- presets.by_name(config_name, include_path)
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)}
318
331
  end
319
332
 
320
- def get_plugin_default_config_name(plugin_name_sym)
321
- presets.plugin_default_name(plugin_name_sym)
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: -> {}
322
341
  end
323
342
 
324
- def save_config_file_if_needed
325
- presets.save_if_needed
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}]
326
347
  end
327
348
 
328
- def lookup_preset(url:, username:)
329
- presets.lookup_preset(url: url, username: username)
330
- end
349
+ attr_accessor :option_cache_tokens
331
350
 
332
351
  attr_reader :gem_url
333
- attr_accessor :option_config_file
334
352
 
335
353
  def option_plugin_folder=(value)
336
354
  value = [value] unless value.is_a?(Array)
@@ -349,554 +367,222 @@ module Aspera
349
367
  when Hash
350
368
  options.add_option_preset(value, 'set')
351
369
  when String
352
- options.add_option_preset(preset_by_name(value), 'set_by_name')
370
+ options.add_option_preset(presets.by_name(value), 'set_by_name')
353
371
  else
354
372
  raise BadArgument, 'Preset definition must be a String for preset name, or Hash for set of values'
355
373
  end
356
374
  end
357
375
 
358
- def install_transfer_sdk
359
- asked_version = options.get_next_argument('transferd version', mandatory: false)
360
- sdk_url = options.get_option(:sdk_url, mandatory: true)
361
- sdk_url = nil if sdk_url.eql?(SpecialValues::DEF)
362
- name, version, folder = Ascp::Installation.instance.retrieve_sdk(url: sdk_url, version: asked_version)
363
- return Result::Status.new("Installed #{name} version #{version} in #{folder}")
364
- end
365
-
366
- def execute_action_ascp
367
- command = options.get_next_command(%i[show products info install spec schema errors])
368
- case command
369
- when :show
370
- return Result::Text.new(Ascp::Installation.instance.path(:ascp))
371
- when :info
372
- # Collect info from ascp executable
373
- data = Ascp::Installation.instance.ascp_info
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])
376
+ # DSL handlers - one method per leaf command
377
+
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])
386
392
  end
387
- when :install
388
- return install_transfer_sdk
389
- when :spec
390
- builder = Schema::Documentation.new(TerminalFormatter, Transfer::Spec::SCHEMA, include_option: true, agent_columns: true).build
391
- return Result::ObjectList.new(builder.rows, fields: builder.columns)
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])
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)
402
398
  end
403
- return Result::ObjectList.new(error_data)
404
- else Aspera.error_unexpected_value(command)
405
- end
406
- Aspera.error_unreachable_line
407
- end
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)
399
+ if Environment.instance.url_method.eql?(:graphical)
400
+ Environment.instance.open_uri("file://#{local_doc}")
401
+ return Result::Nothing.new
402
+ end
403
+ return Result::Text.new(content)
421
404
  end
422
- Aspera.error_unreachable_line
423
405
  end
424
406
 
425
- # Legacy actions available globally
426
- PRESET_GBL_ACTIONS = %i[list overview lookup secure].freeze
427
- # Operations requiring that preset exists
428
- PRESET_EXIST_ACTIONS = %i[show delete get unset].freeze
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
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}")
410
+ end
432
411
 
433
- def execute_preset(action: nil, name: nil)
434
- cp = presets.config_presets
435
- action = options.get_next_command(PRESET_ALL_ACTIONS) if action.nil?
436
- name = options.instance_identifier if name.nil? && PRESET_INSTANCE_ACTIONS.include?(action)
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
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"))
528
416
  end
529
417
 
530
- ACTIONS = %i[
531
- preset
532
- open
533
- documentation
534
- genkey
535
- pubkey
536
- remote_certificate
537
- gem
538
- plugins
539
- tokens
540
- echo
541
- download
542
- wizard
543
- detect
544
- coffee
545
- image
546
- ascp
547
- sync
548
- transferd
549
- email_test
550
- smtp_settings
551
- proxy_check
552
- folder
553
- file
554
- check_update
555
- initdemo
556
- vault
557
- test
558
- platform
559
- completion
560
- ].freeze
561
-
562
- # Main action procedure for plugin
563
- def execute_action
564
- action = options.get_next_command(ACTIONS)
565
- case action
566
- when :preset # Newer syntax
567
- return execute_preset
568
- when :open
569
- Environment.instance.open_editor(@option_config_file.to_s)
570
- return Result::Nothing.new
571
- when :documentation
572
- section = options.get_next_argument('private key file path', mandatory: false)
573
- section = "##{section}" unless section.nil?
574
- Environment.instance.open_uri("#{Info::DOC_URL}#{section}")
575
- return Result::Nothing.new
576
- when :genkey # Generate new rsa key
577
- private_key_path = options.get_next_argument('private key file path')
578
- private_key_length = options.get_next_argument('size in bits', mandatory: false, validation: Integer, default: OAuth::Jwt::DEFAULT_PRIV_KEY_LENGTH)
579
- OAuth::Jwt.generate_rsa_private_key(path: private_key_path, length: private_key_length)
580
- return Result::Status.new("Generated #{private_key_length} bit RSA key: #{private_key_path}")
581
- when :pubkey # Get pub key
582
- private_key_pem = options.get_next_argument('private key PEM value')
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
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)
422
+ end
423
+
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])
428
+ end
429
+
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}")
436
+ end
437
+
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)
443
+ end
444
+
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
+ }
454
+ end
455
+ Result::ObjectList.new(result, fields: %w[plugin detect wizard path])
456
+ end
457
+
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}')
647
471
  end
648
472
  end
649
473
  end
650
- END_OF_PLUGIN_CODE
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}"}
474
+ end
724
475
  end
725
- return Result::Status.new('Done')
726
- when :vault then execute_vault
727
- when :test then return execute_test
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
476
+ END_OF_PLUGIN_CODE
477
+ File.write(plugin_file, content)
478
+ Result::Status.new("Created #{plugin_file}")
734
479
  end
735
480
 
736
- # Generate shell completion
737
- # @return [Result] completion result
738
- def execute_completion
739
- shell_type = options.get_next_command(%i[bash])
740
- case shell_type
741
- when :bash
742
- if options.get_next_argument('', multiple: true, mandatory: false).nil?
743
- Plugins::Factory.instance.plugin_list.each{ |p| puts p}
744
- else
745
- Log.log.warn('only first level completion so far')
746
- end
747
- Process.exit(0)
748
- else Aspera.error_unreachable_line
749
- end
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)
485
+ end
486
+
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)
750
491
  end
751
492
 
752
- # @return [Hash] email server setting with defaults if not defined
753
- def email_settings
754
- smtp = options.get_option(:smtp, mandatory: true)
755
- # Change keys from string into symbol
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
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}"}
766
497
  else
767
- 25
768
- end
769
- smtp[:from_email] ||= smtp[:username] if smtp.key?(:username)
770
- smtp[:from_name] ||= smtp[:from_email].sub(/@.*$/, '').gsub(/[^a-zA-Z]/, ' ').capitalize if smtp.key?(:username)
771
- smtp[:domain] ||= smtp[:from_email].sub(/^.*@/, '') if smtp.key?(:from_email)
772
- # Check minimum required
773
- %i[server port domain].each do |n|
774
- Aspera.assert(smtp.key?(n)){"Missing mandatory smtp parameter: #{n}"}
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
+ }
775
504
  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"}
791
- end
792
- start_options = [mail_conf[:domain]]
793
- start_options.push(mail_conf[:username], mail_conf[:password], :login) if mail_conf.key?(:username) && mail_conf.key?(:password)
794
- # Create a binding with only variables defined in values
795
- template_binding = Environment.empty_binding
796
- # Add variables to binding
797
- values.each do |k, v|
798
- Aspera.assert_type(k, Symbol)
799
- template_binding.local_variable_set(k, v)
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}"}
800
513
  end
801
- # Execute template
802
- msg_with_headers = ERB.new(notify_template).result(template_binding)
803
- Log.dump(:msg_with_headers, msg_with_headers)
804
- require 'net/smtp'
805
- smtp = Net::SMTP.new(mail_conf[:server], mail_conf[:port])
806
- smtp.enable_starttls if mail_conf[:tls]
807
- smtp.enable_tls if mail_conf[:ssl]
808
- smtp.start(*start_options) do |smtp_session|
809
- smtp_session.send_message(msg_with_headers, values[:from_email], values[:to])
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
810
539
  end
811
- nil
812
- end
813
-
814
- # @return [Hash] result of execution of vault command
815
- def execute_vault
816
- command = options.get_next_command(%i[info list show create delete password])
817
- case command
818
- when :info
819
- return Result::SingleObject.new(vault.info)
820
- when :list
821
- # , fields: %w(label url username password description)
822
- return Result::ObjectList.new(vault.list)
823
- when :show
824
- return Result::SingleObject.new(vault.get(label: options.get_next_argument('label')))
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')
540
+ Result::ObjectList.new(commands, fields: %w[syntax description])
541
+ end
542
+
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
836
554
  end
555
+ Result::ObjectList.new(rows, fields: %w[option description allowed deprecated])
837
556
  end
838
557
 
839
- # @return [String] value from vault matching <name>.<param>
840
- def vault_value(name)
841
- m = name.split('.')
842
- raise BadArgument, 'vault name shall match <name>.<param>' unless m.length.eql?(2)
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
- )
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
862
562
  end
863
563
 
864
- # Artificially raise an exception for tests
865
- def execute_test
866
- case options.get_next_command(%i[throw web])
867
- when :throw
868
- # :type [String]
869
- # Options
870
- exception_class_name = options.get_next_argument('exception class name', mandatory: true)
871
- exception_text = options.get_next_argument('exception text', mandatory: true)
872
- type = Object.const_get(exception_class_name)
873
- Aspera.assert(type <= Exception){"#{type} is not an exception: #{type.class}"}
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']
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)
890
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}
891
578
  end
892
- return secret
579
+ Process.exit(0)
893
580
  end
581
+
894
582
  # Folder in $HOME for application files (~/.aspera)
895
583
  ASPERA_HOME_FOLDER_NAME = '.aspera'
896
584
  # Default config file name
897
585
  DEFAULT_CONFIG_FILENAME = 'config.yaml'
898
- # Used in execute_preset / execute_action to identify the global default keyword
899
- GLOBAL_DEFAULT_KEYWORD = 'GLOBAL'
900
586
  CONF_GLOBAL_SYM = :config
901
587
  # Folder containing custom plugins in user's config folder
902
588
  ASPERA_PLUGINS_FOLDERNAME = 'plugins'
@@ -905,7 +591,6 @@ module Aspera
905
591
  REST_EXCEPTIONS_LOG_FILENAME = 'rest_exceptions.log'
906
592
  ASPERA = 'aspera'
907
593
  SERVER_COMMAND = 'server'
908
- TRANSFERD_APP_NAME = 'sdk'
909
594
  DEMO_SERVER = 'demo'
910
595
  DEMO_PRESET = 'demoserver' # cspell: disable-line
911
596
  EMAIL_TEST_TEMPLATE = <<~END_OF_TEMPLATE
@@ -918,15 +603,9 @@ module Aspera
918
603
  # Special extended values
919
604
  EXTEND_PRESET = :preset
920
605
  EXTEND_VAULT = :vault
921
- EXTEND_ARGS = :''
922
- DEFAULT_CHECK_NEW_VERSION_DAYS = 7
923
606
  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
607
  private_constant :ASPERA_HOME_FOLDER_NAME,
928
608
  :DEFAULT_CONFIG_FILENAME,
929
- :GLOBAL_DEFAULT_KEYWORD,
930
609
  :CONF_GLOBAL_SYM,
931
610
  :ASPERA_PLUGINS_FOLDERNAME,
932
611
  :PERSISTENCY_FOLDER,
@@ -934,17 +613,12 @@ module Aspera
934
613
  :REST_EXCEPTIONS_LOG_FILENAME,
935
614
  :ASPERA,
936
615
  :SERVER_COMMAND,
937
- :TRANSFERD_APP_NAME,
938
616
  :DEMO_SERVER,
939
617
  :DEMO_PRESET,
940
618
  :EMAIL_TEST_TEMPLATE,
941
619
  :EXTEND_PRESET,
942
620
  :EXTEND_VAULT,
943
- :DEFAULT_CHECK_NEW_VERSION_DAYS,
944
- :COFFEE_IMAGE_URL,
945
- :GEM_CHECK_DATE_FMT,
946
- :CONF_OVERVIEW_KEYS,
947
- :SMTP_CONF_PARAMS
621
+ :COFFEE_IMAGE_URL
948
622
  end
949
623
  end
950
624
  end