aspera-cli 4.27.0 → 4.27.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +29 -0
  4. data/CONTRIBUTING.md +1 -7
  5. data/TODO.md +50 -0
  6. data/docs/README.md +708 -488
  7. data/lib/aspera/agent/base.rb +10 -2
  8. data/lib/aspera/agent/connect.rb +7 -7
  9. data/lib/aspera/agent/desktop.rb +3 -3
  10. data/lib/aspera/agent/direct.rb +48 -19
  11. data/lib/aspera/agent/factory.rb +3 -3
  12. data/lib/aspera/agent/httpgw.rb +55 -14
  13. data/lib/aspera/agent/node.rb +2 -2
  14. data/lib/aspera/agent/transferd.rb +12 -12
  15. data/lib/aspera/api/aoc.rb +28 -28
  16. data/lib/aspera/api/cos_node.rb +10 -9
  17. data/lib/aspera/api/faspex.rb +2 -2
  18. data/lib/aspera/api/httpgw.rb +20 -20
  19. data/lib/aspera/api/node.rb +31 -53
  20. data/lib/aspera/ascmd.rb +13 -13
  21. data/lib/aspera/ascp/installation.rb +43 -36
  22. data/lib/aspera/ascp/management.rb +6 -6
  23. data/lib/aspera/assert.rb +12 -13
  24. data/lib/aspera/cli/ascp_actions.rb +7 -7
  25. data/lib/aspera/cli/async_transfer_store.rb +49 -5
  26. data/lib/aspera/cli/bootstrapper.rb +9 -9
  27. data/lib/aspera/cli/command_registry.rb +1 -1
  28. data/lib/aspera/cli/command_spec.rb +6 -3
  29. data/lib/aspera/cli/context.rb +3 -3
  30. data/lib/aspera/cli/extended_value.rb +26 -26
  31. data/lib/aspera/cli/formatter.rb +76 -54
  32. data/lib/aspera/cli/gem_checker.rb +1 -1
  33. data/lib/aspera/cli/hints.rb +2 -2
  34. data/lib/aspera/cli/http.rb +22 -29
  35. data/lib/aspera/cli/mailer.rb +7 -7
  36. data/lib/aspera/cli/mcp_tool.rb +31 -4
  37. data/lib/aspera/cli/option_declarator.rb +77 -0
  38. data/lib/aspera/cli/options.schema.yaml +273 -3
  39. data/lib/aspera/cli/parser.rb +539 -280
  40. data/lib/aspera/cli/plugins/alee.rb +1 -1
  41. data/lib/aspera/cli/plugins/aoc.rb +297 -313
  42. data/lib/aspera/cli/plugins/ats.rb +25 -21
  43. data/lib/aspera/cli/plugins/base.rb +357 -186
  44. data/lib/aspera/cli/plugins/basic_auth.rb +0 -12
  45. data/lib/aspera/cli/plugins/config.rb +76 -73
  46. data/lib/aspera/cli/plugins/console.rb +8 -8
  47. data/lib/aspera/cli/plugins/cos.rb +4 -3
  48. data/lib/aspera/cli/plugins/factory.rb +2 -2
  49. data/lib/aspera/cli/plugins/faspex5.rb +259 -248
  50. data/lib/aspera/cli/plugins/faspio.rb +7 -2
  51. data/lib/aspera/cli/plugins/httpgw.rb +1 -1
  52. data/lib/aspera/cli/plugins/mcp.rb +11 -11
  53. data/lib/aspera/cli/plugins/node.rb +227 -283
  54. data/lib/aspera/cli/plugins/orchestrator.rb +23 -23
  55. data/lib/aspera/cli/plugins/preview.rb +61 -64
  56. data/lib/aspera/cli/plugins/server.rb +36 -31
  57. data/lib/aspera/cli/plugins/shares.rb +174 -135
  58. data/lib/aspera/cli/preset_actions.rb +39 -19
  59. data/lib/aspera/cli/preset_manager.rb +74 -32
  60. data/lib/aspera/cli/result.rb +18 -16
  61. data/lib/aspera/cli/runner.rb +75 -39
  62. data/lib/aspera/cli/secret_finder.rb +1 -1
  63. data/lib/aspera/cli/sync_actions.rb +36 -35
  64. data/lib/aspera/cli/terminal_formatter.rb +2 -2
  65. data/lib/aspera/cli/transfer_actions.rb +17 -10
  66. data/lib/aspera/cli/transfer_agent.rb +44 -44
  67. data/lib/aspera/cli/transfer_progress.rb +7 -7
  68. data/lib/aspera/cli/vault_manager.rb +34 -12
  69. data/lib/aspera/cli/version.rb +1 -1
  70. data/lib/aspera/cli/wizard.rb +17 -20
  71. data/lib/aspera/colors.rb +3 -3
  72. data/lib/aspera/command_line_builder.rb +16 -16
  73. data/lib/aspera/command_line_converter.rb +1 -1
  74. data/lib/aspera/coverage.rb +1 -2
  75. data/lib/aspera/data_repository.rb +1 -1
  76. data/lib/aspera/dot_container.rb +8 -8
  77. data/lib/aspera/environment.rb +12 -12
  78. data/lib/aspera/faspex_gw.rb +4 -4
  79. data/lib/aspera/faspex_postproc.rb +14 -9
  80. data/lib/aspera/graphql.rb +4 -2
  81. data/lib/aspera/hash_ext.rb +2 -2
  82. data/lib/aspera/json_rpc/client.rb +1 -1
  83. data/lib/aspera/keychain/base.rb +10 -3
  84. data/lib/aspera/keychain/encrypted_hash.rb +92 -17
  85. data/lib/aspera/keychain/factory.rb +17 -5
  86. data/lib/aspera/keychain/hashicorp_vault.rb +1 -1
  87. data/lib/aspera/keychain/macos_security.rb +23 -25
  88. data/lib/aspera/keychain/one_password_api.rb +86 -0
  89. data/lib/aspera/keychain/one_password_base.rb +34 -0
  90. data/lib/aspera/keychain/one_password_cli.rb +98 -0
  91. data/lib/aspera/link_header.rb +2 -2
  92. data/lib/aspera/log.rb +13 -13
  93. data/lib/aspera/markdown.rb +5 -5
  94. data/lib/aspera/nagios.rb +8 -8
  95. data/lib/aspera/node_simulator.rb +33 -27
  96. data/lib/aspera/oauth/base.rb +4 -5
  97. data/lib/aspera/oauth/boot.rb +3 -3
  98. data/lib/aspera/oauth/factory.rb +5 -5
  99. data/lib/aspera/oauth/jwt.rb +5 -7
  100. data/lib/aspera/oauth/web.rb +4 -4
  101. data/lib/aspera/persistency_action_once.rb +4 -4
  102. data/lib/aspera/persistency_folder.rb +8 -8
  103. data/lib/aspera/preview/file_types.rb +4 -4
  104. data/lib/aspera/preview/generator.rb +49 -46
  105. data/lib/aspera/preview/options.rb +3 -2
  106. data/lib/aspera/preview/terminal.rb +5 -5
  107. data/lib/aspera/preview/utils.rb +65 -18
  108. data/lib/aspera/products/connect.rb +2 -2
  109. data/lib/aspera/products/desktop.rb +1 -1
  110. data/lib/aspera/products/other.rb +2 -2
  111. data/lib/aspera/products/transferd.rb +3 -3
  112. data/lib/aspera/proxy_auto_config.rb +10 -10
  113. data/lib/aspera/rest.rb +149 -115
  114. data/lib/aspera/rest_error_analyzer.rb +3 -3
  115. data/lib/aspera/rest_list.rb +10 -9
  116. data/lib/aspera/schema/IBM Aspera Node API-4.4.6.yaml +6232 -0
  117. data/lib/aspera/schema/IBM Aspera faspio Gateway API-1.0.0.yaml +249 -0
  118. data/lib/aspera/schema/IBM_Aspera_Shares.yaml +4653 -0
  119. data/lib/aspera/schema/documentation.rb +17 -11
  120. data/lib/aspera/schema/reader.rb +78 -4
  121. data/lib/aspera/schema/registry.rb +26 -7
  122. data/lib/aspera/secret_hider.rb +5 -5
  123. data/lib/aspera/ssh.rb +64 -31
  124. data/lib/aspera/ssl.rb +3 -3
  125. data/lib/aspera/sync/database.rb +2 -2
  126. data/lib/aspera/sync/operations.rb +13 -13
  127. data/lib/aspera/temp_file_manager.rb +24 -4
  128. data/lib/aspera/transfer/faux_file.rb +2 -2
  129. data/lib/aspera/transfer/parameters.rb +15 -15
  130. data/lib/aspera/transfer/resumer.rb +9 -9
  131. data/lib/aspera/transfer/spec.rb +1 -1
  132. data/lib/aspera/uri_reader.rb +52 -15
  133. data/lib/aspera/web_auth.rb +6 -5
  134. data/lib/aspera/web_server_simple.rb +9 -9
  135. data/lib/aspera/yaml.rb +4 -4
  136. data.tar.gz.sig +0 -0
  137. metadata +10 -4
  138. metadata.gz.sig +0 -0
  139. data/lib/aspera/cli/plugins/faspex.rb +0 -576
  140. data/lib/aspera/transfer/uri.rb +0 -56
@@ -12,18 +12,6 @@ module Aspera
12
12
  option :username, description: "User's identifier"
13
13
  option :password, description: "User's password"
14
14
 
15
- # Declare url/username/password on an arbitrary Options object.
16
- # Still needed for ad-hoc callers that are not plugin instances
17
- # (e.g. PresetActions#action_config_lookup).
18
- class << self
19
- def declare_options(options)
20
- options.declare(:url, description: 'URL of application, e.g. https://app.example.com/aspera/app') unless options.option_declared?(:url)
21
- options.declare(:username, description: "User's identifier") unless options.option_declared?(:username)
22
- options.declare(:password, description: "User's password") unless options.option_declared?(:password)
23
- options.parse_options!
24
- end
25
- end
26
-
27
15
  def initialize(context:, basic_options: true)
28
16
  super(context: context)
29
17
  # DSL options (url, username, password) are auto-declared by Base#initialize
@@ -68,7 +68,7 @@ module Aspera
68
68
  # @return [String] product family folder (~/.aspera)
69
69
  def module_family_folder
70
70
  user_home_folder = Dir.home
71
- Aspera.assert(Dir.exist?(user_home_folder), type: Cli::Error){"Home folder does not exist: #{user_home_folder}. Check your user environment."}
71
+ Aspera.assert(Dir.exist?(user_home_folder), type: Cli::Error) { "Home folder does not exist: #{user_home_folder}. Check your user environment." }
72
72
  return File.join(user_home_folder, ASPERA_HOME_FOLDER_NAME)
73
73
  end
74
74
 
@@ -84,7 +84,7 @@ module Aspera
84
84
  private_constant :DEFAULT_CHECK_NEW_VERSION_DAYS
85
85
 
86
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
87
+ option :version_check_days, description: 'Period in days to check new version (zero to disable)', allowed: Type::INTEGER, default: DEFAULT_CHECK_NEW_VERSION_DAYS
88
88
  option :plugin_folder, description: 'Folder where to find additional plugins', handler: :option_plugin_folder
89
89
  option :sdk_url, description: 'Ascp: URL to get Aspera Transfer Executables', default: SpecialValues::DEF
90
90
  option :locations_url, description: 'Ascp: URL to get download locations of Aspera Transfer Daemon', handler: {o: Ascp::Installation.instance, m: :transferd_urls}
@@ -92,7 +92,7 @@ module Aspera
92
92
  option :smtp, schema: Schema::Registry::SMTP_OPTIONS
93
93
  option :notify_to, description: 'Email: Recipient for notification of transfers'
94
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
95
+ option :cache_tokens, description: 'Save and reuse OAuth tokens', allowed: Type::BOOLEAN, default: true, handler: :option_cache_tokens
96
96
 
97
97
  def initialize(**_)
98
98
  super
@@ -100,7 +100,7 @@ module Aspera
100
100
  @sdk_default_location = false
101
101
  @option_cache_tokens = true
102
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}
103
+ options.declare(:no_default, description: 'Do not load default configuration for plugin', allowed: Type::NONE, short: 'N') { presets.use_plugin_defaults = false }
104
104
  # Declare wizard options (Wizard#initialize calls options.declare internally)
105
105
  @wizard = Wizard.new(self, context.main_folder)
106
106
  options.parse_options!
@@ -113,7 +113,7 @@ module Aspera
113
113
 
114
114
  # DSL command declarations - replaces ACTIONS + execute_action
115
115
  command :preset, description: 'Manage configuration presets'
116
- commands_under(:preset) do
116
+ commands_under :preset do
117
117
  command :list, description: 'List all presets'
118
118
  command :overview, description: 'Display all options from all presets'
119
119
  command :lookup, description: 'Find preset matching URL and username'
@@ -127,11 +127,9 @@ module Aspera
127
127
  arguments: [{name: :name, type: :identifier}, {name: :param_name, type: String}]
128
128
  command :unset, description: 'Remove a parameter from a preset',
129
129
  arguments: [{name: :name, type: :identifier}, {name: :param_name, type: String}]
130
- command(
131
- :set, description: 'Set a parameter in a preset',
130
+ command :set, description: 'Set a parameter in a preset',
132
131
  arguments: [{name: :name, type: :identifier}, {name: :param_name, type: String},
133
132
  {name: :param_value, type: nil}]
134
- )
135
133
  command :initialize, description: 'Initialize a preset with a value',
136
134
  arguments: [{name: :name, type: :identifier}, {name: :config_value, type: Hash}]
137
135
  command :update, description: 'Update a preset with current option values',
@@ -159,11 +157,11 @@ module Aspera
159
157
  ]
160
158
  command :pubkey, description: 'Display the public key of an RSA private key',
161
159
  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)}
160
+ action: ->(private_key_pem:, **) { Result::Text.new(OpenSSL::PKey::RSA.new(private_key_pem).public_key.to_s) }
163
161
  command :remote_certificate, description: 'Retrieve the certificate chain of a remote HTTPS server'
164
162
  command :echo, description: 'Display the value of a given argument',
165
163
  arguments: [{name: :value, type: nil}],
166
- action: ->(value:, **){Result.auto(value)}
164
+ action: ->(value:, **) { Result.auto(value) }
167
165
  command :download, description: 'Download a file from a URL',
168
166
  arguments: [
169
167
  {name: :file_url, type: String},
@@ -176,14 +174,14 @@ module Aspera
176
174
  command :wizard, description: 'Run the setup wizard for an Aspera product (interactive)',
177
175
  arguments: [{name: :url, type: String}, {name: :plugin_name, mandatory: false, default: nil},
178
176
  {name: :preset_name, mandatory: false, default: ''}]
179
- command :coffee, description: 'Display a coffee image', action: ->{Result::Image.new(COFFEE_IMAGE_URL)}
177
+ command :coffee, description: 'Display a coffee image', action: -> { Result::Image.new(COFFEE_IMAGE_URL) }
180
178
  command :image, description: 'Display an image',
181
179
  arguments: [{name: :image_uri, type: nil}],
182
- action: ->(image_uri:, **){Result::Image.new(image_uri)}
180
+ action: ->(image_uri:, **) { Result::Image.new(image_uri) }
183
181
  command :sync, description: 'Manage Aspera Sync operations'
184
182
  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)}
183
+ command :folder, description: 'Display the configuration folder path', action: -> { Result::Text.new(context.main_folder) }
184
+ command :file, description: 'Display the configuration file path', action: -> { Result::Text.new(context.presets.config_file) }
187
185
  command(
188
186
  :email_test, description: 'Send a test email',
189
187
  action: lambda do
@@ -191,41 +189,45 @@ module Aspera
191
189
  Result::Nothing.new
192
190
  end
193
191
  )
194
- command :smtp_settings, description: 'Display the current SMTP settings', action: ->{Result::SingleObject.new(context.mailer.email_settings)}
192
+ command :smtp_settings, description: 'Display the current SMTP settings', action: -> { Result::SingleObject.new(context.mailer.email_settings) }
195
193
  command(
196
194
  :proxy_check, description: 'Check the proxy returned by the PAC script for a given URL',
197
195
  arguments: [{name: :server_url, type: String}],
198
196
  action: lambda do |server_url:, **|
199
- Aspera.assert(!context.pac_executor.nil?, type: Cli::BadArgument){'No PAC script configured, use --fpac'}
197
+ Aspera.assert(!context.pac_executor.nil?, type: Cli::BadArgument) { 'No PAC script configured, use --fpac' }
200
198
  Result::ValueList.new(context.pac_executor.get_proxies(server_url), name: 'proxy')
201
199
  end
202
200
  )
203
- command :check_update, description: 'Check if a newer version of the gem is available', action: ->{Result::SingleObject.new(check_gem_version)}
201
+ command :check_update, description: 'Check if a newer version of the gem is available', action: -> { Result::SingleObject.new(check_gem_version) }
204
202
  command :initdemo, description: 'Initialize the demo server preset'
205
203
  command :vault, description: 'Manage secrets in the vault'
206
- commands_under(:vault) do
204
+ commands_under :vault do
207
205
  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}]
206
+ action: ->(**) { Result::SingleObject.new(vault_required.info) }
207
+ command :ids, description: 'List secret labels in the vault',
208
+ action: ->(**) { Result::ObjectList.new(vault_required.ids) }
209
+ command :list, description: 'List all secrets with full details',
210
+ action: ->(**) { Result::ObjectList.new(vault_required.all) }
211
+ command :show, description: 'Show a secret by label (or id)',
212
+ arguments: [{name: :label, type: String}, {name: :id, type: String, mandatory: false, default: nil}]
213
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}]
214
+ arguments: [{name: :info, type: Hash, schema: Schema::Registry::VAULT_SECRET}]
215
+ command :delete, description: 'Delete a secret by label (or id)',
216
+ arguments: [{name: :label, type: String}, {name: :id, type: String, mandatory: false, default: nil}]
217
217
  command :password, description: 'Change the vault password',
218
218
  arguments: [{name: :new_password, type: String}]
219
+ command :import, description: 'Import secrets from a JSON array (supports --bulk)',
220
+ arguments: [{name: :secrets, type: Array, schema: {type: 'array', items: {'$ref' => Schema::Registry::VAULT_SECRET}}}]
219
221
  end
220
222
  command :commands, description: 'List all available commands across all plugins'
221
223
  command :options, description: 'List all options available for a plugin',
222
224
  arguments: [{name: :plugin_name, type: String}]
223
225
  command :test, description: 'Internal test commands'
224
- command :platform, description: 'Display the current platform/architecture', action: ->{Result::Text.new(Environment.instance.architecture)}
226
+ command :platform, description: 'Display the current platform/architecture', action: -> { Result::Text.new(Environment.instance.architecture) }
225
227
  command :completion, description: 'Generate shell completion scripts'
226
228
 
227
229
  # remote_certificate sub-commands
228
- commands_under(:remote_certificate) do
230
+ commands_under :remote_certificate do
229
231
  command :chain, description: 'Display the full certificate chain as PEM',
230
232
  arguments: [{name: :remote_url, type: String}]
231
233
  command :only, description: 'Display only the server certificate as PEM',
@@ -235,7 +237,7 @@ module Aspera
235
237
  end
236
238
 
237
239
  # tokens sub-commands
238
- commands_under(:tokens) do
240
+ commands_under :tokens do
239
241
  command(
240
242
  :flush, description: 'Delete all cached OAuth tokens',
241
243
  action: lambda do
@@ -255,7 +257,7 @@ module Aspera
255
257
  end
256
258
 
257
259
  # plugins sub-commands
258
- commands_under(:plugins) do
260
+ commands_under :plugins do
259
261
  command :list, description: 'List all available plugins'
260
262
  command :create, description: 'Create a new plugin skeleton file',
261
263
  arguments: [
@@ -266,7 +268,7 @@ module Aspera
266
268
 
267
269
  # ascp sub-commands
268
270
  command :ascp, description: 'Manage FASP/ascp transfer engine'
269
- commands_under(:ascp) do
271
+ commands_under :ascp do
270
272
  command :show, description: 'Display ascp binary path'
271
273
  command :info, description: 'Display ascp and transfer spec information'
272
274
  command :install, description: 'Install the transfer SDK',
@@ -276,14 +278,14 @@ module Aspera
276
278
  arguments: [{name: :agent_name, mandatory: false, default: nil}]
277
279
  command :errors, description: 'Display FASP error codes'
278
280
  command :products, description: 'Manage installed Aspera products'
279
- commands_under(%i[ascp products]) do
281
+ commands_under :products do
280
282
  command :list, description: 'List installed Aspera products'
281
283
  end
282
284
  end
283
285
 
284
286
  # agents sub-commands
285
287
  command :agents, description: 'Manage transfer agents'
286
- commands_under(:agents) do
288
+ commands_under :agents do
287
289
  command :list, description: 'List all transfer agents'
288
290
  command :show, description: 'Show details for a transfer agent',
289
291
  arguments: [{name: :agent_name, allowed: Agent::Factory::ALL.keys}]
@@ -293,7 +295,7 @@ module Aspera
293
295
 
294
296
  # transfer async management sub-commands
295
297
  command :transfer, description: 'Manage asynchronous transfers'
296
- commands_under(:transfer) do
298
+ commands_under :transfer do
297
299
  command :list, description: 'List all async transfer jobs'
298
300
  command :status, description: 'Show status of an async transfer job',
299
301
  arguments: [{name: :job_id, type: String}]
@@ -302,13 +304,13 @@ module Aspera
302
304
 
303
305
  # transferd sub-commands
304
306
  command :transferd, description: 'Manage the transfer daemon (transferd)'
305
- commands_under(:transferd) do
307
+ commands_under :transferd do
306
308
  command :install, description: 'Install the transfer daemon'
307
309
  command :list, description: 'List available SDK locations'
308
310
  end
309
311
 
310
312
  # sync sub-commands
311
- commands_under(:sync) do
313
+ commands_under :sync do
312
314
  command(
313
315
  :spec, description: 'Display the sync configuration schema',
314
316
  action: lambda do
@@ -317,21 +319,21 @@ module Aspera
317
319
  end
318
320
  )
319
321
  command :admin, description: 'Manage sync database (admin operations)'
320
- SyncActions.register_sync_admin_commands(self, %i[sync admin])
322
+ SyncActions.register_sync_admin_commands(self, :admin)
321
323
  command :translate, description: 'Translate async-style arguments to sync config format',
322
324
  arguments: [{name: :async_arguments, type: String, multiple: true}],
323
- action: ->(async_arguments:, **){Result::SingleObject.new(Sync::Operations.args_to_conf(async_arguments))}
325
+ action: ->(async_arguments:, **) { Result::SingleObject.new(Sync::Operations.args_to_conf(async_arguments)) }
324
326
  end
325
327
 
326
328
  # 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)}
329
+ commands_under :gem do
330
+ command :path, description: 'Display the gem source root path', action: -> { Result::Text.new(self.class.gem_src_root) }
331
+ command :version, description: 'Display the gem version', action: -> { Result::Text.new(Cli::VERSION) }
332
+ command :name, description: 'Display the gem name', action: -> { Result::Text.new(Info::GEM_NAME) }
331
333
  end
332
334
 
333
335
  # test sub-commands
334
- commands_under(:test) do
336
+ commands_under :test do
335
337
  command :throw, description: 'Raise an exception (for testing)',
336
338
  arguments: [
337
339
  {name: :exception_class_name, type: String},
@@ -341,7 +343,7 @@ module Aspera
341
343
  end
342
344
 
343
345
  # completion sub-commands
344
- commands_under(:completion) do
346
+ commands_under :completion do
345
347
  command :bash, description: 'Generate bash completion script',
346
348
  arguments: [{name: :words, type: String, multiple: true, mandatory: false}]
347
349
  end
@@ -352,8 +354,8 @@ module Aspera
352
354
 
353
355
  def option_plugin_folder=(value)
354
356
  value = [value] unless value.is_a?(Array)
355
- Aspera.assert_array_all(value, String){'plugin folder(s)'}
356
- value.each{ |f| Plugins::Factory.instance.add_lookup_folder(f)}
357
+ Aspera.assert_array_all(value, String) { 'plugin folder(s)' }
358
+ value.each { |f| Plugins::Factory.instance.add_lookup_folder(f) }
357
359
  end
358
360
 
359
361
  def option_plugin_folder
@@ -384,7 +386,7 @@ module Aspera
384
386
  when :toc, :local
385
387
  require 'aspera/markdown'
386
388
  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}"}
389
+ Aspera.assert(File.exist?(local_doc), type: Cli::Error) { "Local documentation not found: #{local_doc}" }
388
390
  content = File.read(local_doc)
389
391
  if location == :toc
390
392
  entries = Markdown.toc(content)
@@ -393,7 +395,7 @@ module Aspera
393
395
  # :local
394
396
  if section
395
397
  text = Markdown.extract_section(content, section)
396
- Aspera.assert(!text.nil?, type: Cli::Error){"Section not found: #{section}"}
398
+ Aspera.assert(!text.nil?, type: Cli::Error) { "Section not found: #{section}" }
397
399
  return Result::Text.new(text)
398
400
  end
399
401
  if Environment.instance.url_method.eql?(:graphical)
@@ -411,34 +413,34 @@ module Aspera
411
413
 
412
414
  def action_remote_certificate_chain(remote_url:, **)
413
415
  remote_chain = Rest.remote_certificate_chain(remote_url, as_string: false)
414
- Aspera.assert(remote_chain&.first){"No certificate found for #{remote_url}"}
416
+ Aspera.assert(remote_chain&.first) { "No certificate found for #{remote_url}" }
415
417
  Result::Text.new(remote_chain.map(&:to_pem).join("\n"))
416
418
  end
417
419
 
418
420
  def action_remote_certificate_only(remote_url:, **)
419
421
  remote_chain = Rest.remote_certificate_chain(remote_url, as_string: false)
420
- Aspera.assert(remote_chain&.first){"No certificate found for #{remote_url}"}
422
+ Aspera.assert(remote_chain&.first) { "No certificate found for #{remote_url}" }
421
423
  Result::Text.new(remote_chain.first.to_pem)
422
424
  end
423
425
 
424
426
  def action_remote_certificate_name(remote_url:, **)
425
427
  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
+ Aspera.assert(remote_chain&.first) { "No certificate found for #{remote_url}" }
429
+ Result::Text.new(remote_chain.first.subject.to_a.find { |name, _, _| name == 'CN' }[1])
428
430
  end
429
431
 
430
432
  def action_download(file_url:, file_dest: nil, **)
431
433
  file_url = file_url.chomp
432
434
  file_dest = File.join(transfer.destination_folder(Transfer::Spec::DIRECTION_RECEIVE), file_url.gsub(%r{.*/}, '')) if file_dest.nil?
433
435
  Log.log.info("Downloading: #{file_url}")
434
- Rest.new(base_url: file_url).call(operation: 'GET', save_to_file: file_dest)
436
+ Rest.new(base_url: file_url).call(operation: 'GET', save_to: file_dest)
435
437
  Result::Status.new("Saved to: #{file_dest}")
436
438
  end
437
439
 
438
440
  def action_tokens_show(token_id:, **)
439
441
  require 'aspera/api/node'
440
442
  data = OAuth::Factory.instance.get_token_info(token_id)
441
- Aspera.assert(!data.nil?, type: Cli::Error){'Unknown identifier'}
443
+ Aspera.assert(!data.nil?, type: Cli::Error) { 'Unknown identifier' }
442
444
  Result::SingleObject.new(data)
443
445
  end
444
446
 
@@ -493,9 +495,9 @@ module Aspera
493
495
  def action_initdemo
494
496
  cp = presets.config_presets
495
497
  if cp.key?(DEMO_PRESET)
496
- Log.log.warn{"Demo server preset already present: #{DEMO_PRESET}"}
498
+ Log.log.warn { "Demo server preset already present: #{DEMO_PRESET}" }
497
499
  else
498
- Log.log.info{"Creating Demo server preset: #{DEMO_PRESET}"}
500
+ Log.log.info { "Creating Demo server preset: #{DEMO_PRESET}" }
499
501
  cp[DEMO_PRESET] = {
500
502
  'url' => "ssh://#{DEMO_SERVER}.asperasoft.com:33001",
501
503
  'username' => ASPERA,
@@ -504,12 +506,12 @@ module Aspera
504
506
  end
505
507
  cp[PresetManager::Key::DEFAULTS] ||= {}
506
508
  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
+ Log.log.warn { "Server default preset already set to: #{cp[PresetManager::Key::DEFAULTS][SERVER_COMMAND]}" }
510
+ Log.log.warn { "Use #{DEMO_PRESET} for demo: -P#{DEMO_PRESET}" } unless
509
511
  DEMO_PRESET.eql?(cp[PresetManager::Key::DEFAULTS][SERVER_COMMAND])
510
512
  else
511
513
  cp[PresetManager::Key::DEFAULTS][SERVER_COMMAND] = DEMO_PRESET
512
- Log.log.info{"Setting server default preset to : #{DEMO_PRESET}"}
514
+ Log.log.info { "Setting server default preset to : #{DEMO_PRESET}" }
513
515
  end
514
516
  Result::Status.new('Done')
515
517
  end
@@ -518,19 +520,20 @@ module Aspera
518
520
  commands = Plugins::Factory.instance.plugin_list.flat_map do |name|
519
521
  plugin_class = Plugins::Factory.instance.plugin_class(name)
520
522
  reg = plugin_class.command_registry
521
- reg.all_paths.reject{ |path| reg.children_of(path).any?}.map do |path|
523
+ reg.all_paths.reject { |path| reg.children_of(path).any? }.map do |path|
522
524
  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}>]"
525
+ # Build syntax by interleaving each path segment with the arguments declared on that node
526
+ tokens = [name.to_s]
527
+ path.each_with_index do |seg, i|
528
+ tokens << seg.to_s
529
+ node_args = reg[path[0, i + 1]]&.arguments.to_a
530
+ node_args.each do |a|
531
+ token = a.allowed ? a.allowed.join('|') : a.name.to_s
532
+ token += '...' if a.multiple
533
+ tokens << (a.mandatory ? "<#{token}>" : "[<#{token}>]")
534
+ end
532
535
  end
533
- syntax = ([name.to_s] + path.map(&:to_s) + arg_tokens).join(' ')
536
+ syntax = tokens.join(' ')
534
537
  {
535
538
  syntax: syntax,
536
539
  description: spec&.description.to_s
@@ -557,14 +560,14 @@ module Aspera
557
560
 
558
561
  def action_test_throw(exception_class_name:, exception_text:, **)
559
562
  type = Object.const_get(exception_class_name)
560
- Aspera.assert(type <= Exception){"#{type} is not an exception: #{type.class}"}
563
+ Aspera.assert(type <= Exception) { "#{type} is not an exception: #{type.class}" }
561
564
  raise type, exception_text
562
565
  end
563
566
 
564
567
  def action_completion_bash(words: nil, **)
565
568
  if words.nil? || words.empty?
566
569
  # Level 0: propose plugin names
567
- Plugins::Factory.instance.plugin_list.each{ |p| puts p}
570
+ Plugins::Factory.instance.plugin_list.each { |p| puts p }
568
571
  else
569
572
  plugin_sym = words.first.to_sym
570
573
  plugin_class = begin
@@ -574,7 +577,7 @@ module Aspera
574
577
  end
575
578
  # Navigate into the registry using remaining words as path
576
579
  path = words[1..].map(&:to_sym)
577
- plugin_class.command_registry.children_of(path).each_key{ |k| puts k}
580
+ plugin_class.command_registry.children_of(path).each_key { |k| puts k }
578
581
  end
579
582
  Process.exit(0)
580
583
  end
@@ -42,7 +42,7 @@ module Aspera
42
42
  }
43
43
  rescue StandardError => e
44
44
  error = e
45
- Log.log.debug{"detect error: #{e}"}
45
+ Log.log.debug { "detect error: #{e}" }
46
46
  end
47
47
  raise error if error
48
48
  return
@@ -76,16 +76,16 @@ module Aspera
76
76
  command :health, description: 'Check Console API health', setup: :setup_api
77
77
  command :transfer, description: 'Manage transfers', setup: :setup_api
78
78
 
79
- commands_under(:transfer) do
79
+ commands_under :transfer do
80
80
  command :current, description: 'Manage current transfers'
81
81
  command :smart, description: 'Manage smart transfers'
82
82
  end
83
83
 
84
- commands_under(%i[transfer current]) do
84
+ commands_under %i[transfer current] do
85
85
  command :list, description: 'List current transfers'
86
86
  command :show, description: 'Show a transfer',
87
87
  arguments: [{name: :transfer_id, type: :identifier}],
88
- action: ->(api_console:, transfer_id:, **){Result::SingleObject.new(api_console.read("transfers/#{transfer_id}"))}
88
+ action: ->(api_console:, transfer_id:, **) { Result::SingleObject.new(api_console.read("transfers/#{transfer_id}")) }
89
89
  command :files, description: 'List files in a transfer',
90
90
  arguments: [{name: :transfer_id, type: :identifier}]
91
91
  command :start, description: 'Start a transfer',
@@ -117,8 +117,8 @@ module Aspera
117
117
  end
118
118
  end
119
119
 
120
- commands_under(%i[transfer smart]) do
121
- command :list, description: 'List smart transfers', action: ->(api_console:){Result::ObjectList.new(api_console.read('smart_transfers'))}
120
+ commands_under %i[transfer smart] do
121
+ command :list, description: 'List smart transfers', action: ->(api_console:) { Result::ObjectList.new(api_console.read('smart_transfers')) }
122
122
  command :submit, description: 'Submit a smart transfer',
123
123
  arguments: [{name: :smart_id}, {name: :transfer_params, type: Hash}]
124
124
  end
@@ -175,10 +175,10 @@ module Aspera
175
175
  private
176
176
 
177
177
  def parse_extended_filter(filter, query)
178
- Aspera.assert(filter.start_with?('(') && filter.end_with?(')'), type: BadArgument){"Invalid filter syntax: #{filter}, shall be (field op val)and(field op val)..."}
178
+ Aspera.assert(filter.start_with?('(') && filter.end_with?(')'), type: BadArgument) { "Invalid filter syntax: #{filter}, shall be (field op val)and(field op val)..." }
179
179
  filter[1..-2].split(')and(').each_with_index do |expr, i|
180
180
  m = expr.match(EXPR_RE)
181
- Aspera.assert(m, type: BadArgument){"Invalid expression: #{expr}, shall be: <field> <op> <val>"}
181
+ Aspera.assert(m, type: BadArgument) { "Invalid expression: #{expr}, shall be: <field> <op> <val>" }
182
182
  t = m.captures
183
183
  i += 1
184
184
  query["filter#{i}"] = t[0]
@@ -13,9 +13,9 @@ module Aspera
13
13
 
14
14
  command :node, description: 'Execute COS node commands', setup: :setup_cos_node
15
15
 
16
- commands_under(:node) do
16
+ commands_under :node do
17
17
  Node::COMMANDS_COS.each do |cmd|
18
- command(cmd, description: "Node #{cmd} command")
18
+ command cmd, description: "Node #{cmd} command"
19
19
  end
20
20
  end
21
21
 
@@ -27,10 +27,11 @@ module Aspera
27
27
  option :region, description: 'Storage region'
28
28
  option :identity, description: "Authentication URL (#{Api::CosNode::IBM_CLOUD_TOKEN_URL})", default: Api::CosNode::IBM_CLOUD_TOKEN_URL
29
29
 
30
+ use_options Node
31
+
30
32
  def initialize(**_)
31
33
  super
32
34
  options.parse_options!
33
- Node.declare_options(options)
34
35
  end
35
36
 
36
37
  # Build the COS Node API and plugin from CLI options.
@@ -53,13 +53,13 @@ module Aspera
53
53
 
54
54
  # @return [String] path to source file of plugin
55
55
  def plugin_source(plugin_name_sym)
56
- Aspera.assert(@plugins.key?(plugin_name_sym), type: NoSuchElement){"plugin not found: #{plugin_name_sym}"}
56
+ Aspera.assert(@plugins.key?(plugin_name_sym), type: NoSuchElement) { "plugin not found: #{plugin_name_sym}" }
57
57
  @plugins[plugin_name_sym][:source]
58
58
  end
59
59
 
60
60
  # @return [Class] class object for plugin
61
61
  def plugin_class(plugin_name_sym)
62
- Aspera.assert(@plugins.key?(plugin_name_sym), type: NoSuchElement){"plugin not found: #{plugin_name_sym}"}
62
+ Aspera.assert(@plugins.key?(plugin_name_sym), type: NoSuchElement) { "plugin not found: #{plugin_name_sym}" }
63
63
  require @plugins[plugin_name_sym][:require_stanza]
64
64
  # Module.nesting[1] is Aspera::Cli::Plugins
65
65
  return Object.const_get("#{Module.nesting[1]}::#{plugin_name_sym.to_s.snake_to_capital}")