aspera-cli 4.26.1 → 4.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +300 -22
  4. data/CONTRIBUTING.md +100 -12
  5. data/bin/ascli +5 -5
  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 +27 -25
  12. data/lib/aspera/agent/factory.rb +4 -0
  13. data/lib/aspera/agent/httpgw.rb +2 -2
  14. data/lib/aspera/agent/node.rb +34 -2
  15. data/lib/aspera/agent/transferd.rb +39 -3
  16. data/lib/aspera/api/alee.rb +1 -1
  17. data/lib/aspera/api/aoc.rb +56 -51
  18. data/lib/aspera/api/cos_node.rb +2 -2
  19. data/lib/aspera/api/faspex.rb +22 -20
  20. data/lib/aspera/api/httpgw.rb +4 -4
  21. data/lib/aspera/api/node.rb +33 -30
  22. data/lib/aspera/api/queries/bss_subscription_account.graphql +18 -0
  23. data/lib/aspera/api/queries/bss_subscription_usage.graphql +18 -0
  24. data/lib/aspera/ascmd.rb +25 -23
  25. data/lib/aspera/ascp/installation.rb +43 -40
  26. data/lib/aspera/ascp/management.rb +2 -3
  27. data/lib/aspera/cli/ascp_actions.rb +155 -0
  28. data/lib/aspera/cli/async_transfer_store.rb +81 -0
  29. data/lib/aspera/cli/bootstrapper.rb +197 -0
  30. data/lib/aspera/cli/command_registry.rb +145 -0
  31. data/lib/aspera/cli/command_spec.rb +138 -0
  32. data/lib/aspera/cli/context.rb +71 -0
  33. data/lib/aspera/cli/error.rb +16 -0
  34. data/lib/aspera/cli/extended_value.rb +5 -3
  35. data/lib/aspera/cli/formatter.rb +122 -209
  36. data/lib/aspera/cli/gem_checker.rb +65 -0
  37. data/lib/aspera/cli/hints.rb +2 -2
  38. data/lib/aspera/cli/http.rb +218 -0
  39. data/lib/aspera/cli/info.rb +2 -0
  40. data/lib/aspera/cli/mailer.rb +97 -0
  41. data/lib/aspera/cli/mcp_tool.rb +198 -0
  42. data/lib/aspera/cli/options.schema.yaml +364 -7
  43. data/lib/aspera/cli/{manager.rb → parser.rb} +337 -144
  44. data/lib/aspera/cli/plugins/alee.rb +20 -22
  45. data/lib/aspera/cli/plugins/aoc.rb +1173 -849
  46. data/lib/aspera/cli/plugins/ats.rb +200 -161
  47. data/lib/aspera/cli/plugins/base.rb +470 -107
  48. data/lib/aspera/cli/plugins/basic_auth.rb +14 -4
  49. data/lib/aspera/cli/plugins/config.rb +434 -1032
  50. data/lib/aspera/cli/plugins/console.rb +106 -64
  51. data/lib/aspera/cli/plugins/cos.rb +44 -32
  52. data/lib/aspera/cli/plugins/factory.rb +7 -4
  53. data/lib/aspera/cli/plugins/faspex.rb +296 -259
  54. data/lib/aspera/cli/plugins/faspex5.rb +592 -344
  55. data/lib/aspera/cli/plugins/faspio.rb +49 -51
  56. data/lib/aspera/cli/plugins/httpgw.rb +18 -25
  57. data/lib/aspera/cli/plugins/mcp.rb +279 -0
  58. data/lib/aspera/cli/plugins/node.rb +1001 -797
  59. data/lib/aspera/cli/plugins/oauth.rb +7 -10
  60. data/lib/aspera/cli/plugins/orchestrator.rb +111 -139
  61. data/lib/aspera/cli/plugins/preview.rb +183 -142
  62. data/lib/aspera/cli/plugins/server.rb +125 -80
  63. data/lib/aspera/cli/plugins/shares.rb +301 -107
  64. data/lib/aspera/cli/preset_actions.rb +139 -0
  65. data/lib/aspera/cli/preset_manager.rb +236 -0
  66. data/lib/aspera/cli/result.rb +360 -0
  67. data/lib/aspera/cli/runner.rb +473 -0
  68. data/lib/aspera/cli/secret_finder.rb +40 -0
  69. data/lib/aspera/cli/special_values.rb +1 -0
  70. data/lib/aspera/cli/sync_actions.rb +84 -56
  71. data/lib/aspera/cli/terminal_formatter.rb +65 -0
  72. data/lib/aspera/cli/transfer_actions.rb +83 -0
  73. data/lib/aspera/cli/transfer_agent.rb +116 -51
  74. data/lib/aspera/cli/transfer_progress.rb +9 -9
  75. data/lib/aspera/cli/vault_manager.rb +57 -0
  76. data/lib/aspera/cli/version.rb +1 -1
  77. data/lib/aspera/cli/wizard.rb +26 -26
  78. data/lib/aspera/command_line_builder.rb +23 -23
  79. data/lib/aspera/data_repository.rb +6 -1
  80. data/lib/aspera/dot_container.rb +5 -5
  81. data/lib/aspera/environment.rb +39 -13
  82. data/lib/aspera/exec_spec.rb +13 -0
  83. data/lib/aspera/faspex_gw.rb +2 -2
  84. data/lib/aspera/faspex_postproc.rb +4 -3
  85. data/lib/aspera/graphql.rb +35 -0
  86. data/lib/aspera/hash_ext.rb +6 -0
  87. data/lib/aspera/json_rpc/client.rb +62 -0
  88. data/lib/aspera/json_rpc/version.rb +7 -0
  89. data/lib/aspera/keychain/base.rb +1 -1
  90. data/lib/aspera/keychain/encrypted_hash.rb +2 -2
  91. data/lib/aspera/keychain/factory.rb +4 -4
  92. data/lib/aspera/keychain/macos_security.rb +4 -4
  93. data/lib/aspera/link_header.rb +82 -0
  94. data/lib/aspera/log.rb +23 -5
  95. data/lib/aspera/markdown.rb +85 -2
  96. data/lib/aspera/nagios.rb +2 -2
  97. data/lib/aspera/node_simulator.rb +3 -1
  98. data/lib/aspera/oauth/base.rb +10 -10
  99. data/lib/aspera/oauth/boot.rb +43 -0
  100. data/lib/aspera/oauth/factory.rb +41 -15
  101. data/lib/aspera/oauth/jwt.rb +4 -4
  102. data/lib/aspera/oauth/web.rb +4 -4
  103. data/lib/aspera/oauth.rb +1 -0
  104. data/lib/aspera/persistency_action_once.rb +10 -10
  105. data/lib/aspera/persistency_folder.rb +10 -3
  106. data/lib/aspera/preview/file_types.rb +7 -3
  107. data/lib/aspera/preview/generator.rb +3 -3
  108. data/lib/aspera/preview/utils.rb +1 -1
  109. data/lib/aspera/products/connect.rb +4 -1
  110. data/lib/aspera/products/other.rb +1 -1
  111. data/lib/aspera/products/transferd.rb +3 -3
  112. data/lib/aspera/proxy_auto_config.rb +10 -9
  113. data/lib/aspera/rest.rb +18 -40
  114. data/lib/aspera/rest_error_analyzer.rb +7 -3
  115. data/lib/aspera/rest_errors_aspera.rb +0 -10
  116. data/lib/aspera/rest_list.rb +13 -6
  117. data/lib/aspera/schema/IBM Aspera Faspex API-5.0-enhanced.yaml +14 -4
  118. data/lib/aspera/schema/IBM Aspera on Cloud API-0.2.6-enhanced.yaml +2730 -648
  119. data/lib/aspera/schema/async_tables.yaml +361 -0
  120. data/lib/aspera/schema/documentation.rb +57 -26
  121. data/lib/aspera/schema/reader.rb +60 -11
  122. data/lib/aspera/schema/registry.rb +50 -10
  123. data/lib/aspera/secret_hider.rb +11 -2
  124. data/lib/aspera/ssl.rb +4 -4
  125. data/lib/aspera/sync/conf.schema.yaml +2 -2
  126. data/lib/aspera/sync/database.rb +14 -10
  127. data/lib/aspera/sync/operations.rb +15 -18
  128. data/lib/aspera/temp_file_manager.rb +5 -1
  129. data/lib/aspera/timer_limiter.rb +1 -1
  130. data/lib/aspera/transfer/faux_file.rb +24 -11
  131. data/lib/aspera/transfer/parameters.rb +27 -25
  132. data/lib/aspera/transfer/result.rb +74 -0
  133. data/lib/aspera/transfer/resumer.rb +7 -7
  134. data/lib/aspera/transfer/spec.rb +18 -1
  135. data/lib/aspera/transfer/spec.schema.yaml +26 -8
  136. data/lib/aspera/transfer/uri.rb +1 -1
  137. data/lib/aspera/uri_reader.rb +2 -2
  138. data/lib/aspera/web_auth.rb +2 -2
  139. data/lib/aspera/web_server_simple.rb +8 -5
  140. data/lib/aspera/yaml.rb +2 -1
  141. data.tar.gz.sig +0 -0
  142. metadata +45 -16
  143. metadata.gz.sig +0 -0
  144. data/lib/aspera/cli/main.rb +0 -480
  145. data/lib/aspera/json_rpc.rb +0 -51
@@ -3,6 +3,7 @@
3
3
  # cspell:ignore ascmd zmode zuid zgid fasping
4
4
  require 'aspera/cli/plugins/basic_auth'
5
5
  require 'aspera/cli/sync_actions'
6
+ require 'aspera/transfer/result'
6
7
  require 'aspera/transfer/spec'
7
8
  require 'aspera/ascmd'
8
9
  require 'aspera/ssh'
@@ -21,26 +22,17 @@ module Aspera
21
22
  LOCAL_SCHEME = 'local'
22
23
  HTTPS_SCHEME = 'https'
23
24
  URI_SCHEMES = [SSH_SCHEME, LOCAL_SCHEME, HTTPS_SCHEME].freeze
24
- ASCMD_ALIASES = {
25
- browse: :ls,
26
- delete: :rm,
27
- rename: :mv
28
- }.freeze
29
- TRANSFER_COMMANDS = %i[sync upload download].freeze
30
25
 
31
- private_constant :SSH_SCHEME, :URI_SCHEMES, :ASCMD_ALIASES, :TRANSFER_COMMANDS
26
+ private_constant :SSH_SCHEME, :URI_SCHEMES
32
27
 
33
28
  class LocalExecutor
34
29
  def execute(ascmd_path, input:)
35
30
  Environment.secure_execute(ascmd_path, mode: :capture, stdin_data: input, binmode: true, exception: false).first
36
31
  end
37
32
  end
33
+ application_name 'HSTS Fasp/SSH'
38
34
 
39
35
  class << self
40
- def application_name
41
- 'HSTS Fasp/SSH'
42
- end
43
-
44
36
  # @return [Hash,NilClass]
45
37
  def detect(address_or_url)
46
38
  urls = if address_or_url.match?(%r{^[a-z]{1,6}://})
@@ -87,10 +79,8 @@ module Aspera
87
79
  def initialize(**_)
88
80
  super
89
81
  @connection_type = :ssh
90
- options.declare(:ssh_keys, 'SSH key path list', allowed: Allowed::TYPES_STRING_ARRAY)
91
- options.declare(:passphrase, 'SSH private key passphrase')
92
- options.declare(:ssh_options, 'SSH options', allowed: Hash, default: {})
93
- SyncActions.declare_options(options)
82
+ @ascmd_executor = nil
83
+ @server_transfer_spec = nil
94
84
  options.parse_options!
95
85
  @ssh_opts = options.get_option(:ssh_options).symbolize_keys
96
86
  end
@@ -165,81 +155,136 @@ module Aspera
165
155
  end
166
156
  # if user provided transfer spec has a token, we will use bypass keys
167
157
  cred_set = true if transfer.user_transfer_spec['token'].is_a?(String)
168
- Aspera.assert(cred_set, type: BadArgument){'Either password, key , or transfer spec token must be provided'}
158
+ Aspera.assert(cred_set, 'Either password, key , or transfer spec token must be provided', type: BadArgument)
169
159
  return server_transfer_spec
170
160
  end
171
161
 
172
- def execute_transfer(command, transfer_spec)
173
- case command
174
- when :upload, :download
175
- transfer_spec['direction'] = Transfer::Spec.transfer_type_to_direction(command)
176
- return Main.result_transfer(transfer.start(transfer_spec))
177
- when :sync
178
- # lets ignore the arguments provided by execute_sync_action, we just give the transfer spec
179
- return execute_sync_action{transfer_spec}
162
+ Sync::Operations::DIRECTIONS.each do |dir|
163
+ define_method(:"action_sync_#{dir}"){ |**| run_sync_transfer(dir){@server_transfer_spec}}
164
+ end
165
+
166
+ # --- DSL ---
167
+
168
+ option :ssh_keys, description: 'SSH key path list', allowed: Allowed::TYPES_STRING_ARRAY
169
+ option :passphrase, description: 'SSH private key passphrase'
170
+ option :ssh_options, description: 'SSH options', allowed: Hash
171
+
172
+ # root_setup runs once before any command argument is consumed, populating
173
+ # @server_transfer_spec and @ascmd_executor so that condition methods work.
174
+ root_setup :setup_server
175
+
176
+ command :health, description: 'Check transfer health'
177
+ command(:upload, description: 'Upload files to server', transfer_paths: :send, action: lambda do
178
+ @server_transfer_spec['direction'] = Transfer::Spec.transfer_type_to_direction(:upload)
179
+ Runner.result_transfer(transfer.start(@server_transfer_spec))
180
+ end)
181
+ command(:download, description: 'Download files from server', transfer_paths: :receive, action: lambda do
182
+ @server_transfer_spec['direction'] = Transfer::Spec.transfer_type_to_direction(:download)
183
+ Runner.result_transfer(transfer.start(@server_transfer_spec))
184
+ end)
185
+ command :sync, description: 'Synchronize files with server'
186
+ commands_under(:sync) do
187
+ Sync::Operations::DIRECTIONS.each do |dir|
188
+ command(dir, description: "#{dir.capitalize}-sync with server", transfer_paths: :send)
189
+ end
190
+ command :admin, description: 'Manage sync database (admin operations)'
191
+ SyncActions.register_sync_admin_commands(self, %i[sync admin])
192
+ end
193
+
194
+ commands_under(:health) do
195
+ command :transfer, description: 'Check FASP transfer health'
196
+ end
197
+
198
+ # AsCmd operations (available only when SSH/local - not WSS)
199
+ ASCMD_ARGS = [{name: :command_arguments, multiple: true, mandatory: false, default: nil}].freeze
200
+ private_constant :ASCMD_ARGS
201
+
202
+ command :ls, description: 'List files on server', condition: :ascmd_available?, aliases: [:browse], arguments: ASCMD_ARGS
203
+ command :rm, description: 'Delete file(s) on server', condition: :ascmd_available?, aliases: [:delete], arguments: ASCMD_ARGS
204
+ command :mv, description: 'Rename/move file(s) on server', condition: :ascmd_available?, aliases: [:rename], arguments: ASCMD_ARGS
205
+ command :cp, description: 'Copy file(s) on server', condition: :ascmd_available?, arguments: ASCMD_ARGS
206
+ command :mkdir, description: 'Create directory on server', condition: :ascmd_available?, arguments: ASCMD_ARGS
207
+ command :df, description: 'Show disk usage on server', condition: :ascmd_available?, arguments: ASCMD_ARGS
208
+ command :du, description: 'Show file sizes on server', condition: :ascmd_available?, arguments: ASCMD_ARGS
209
+ command :md5sum, description: 'Compute MD5 checksums on server', condition: :ascmd_available?, arguments: ASCMD_ARGS
210
+ command :info, description: 'Show server system information', condition: :ascmd_available?, arguments: ASCMD_ARGS
211
+
212
+ # Generate ascmd handlers - convention: action_<op>
213
+ %i[rm mv cp mkdir].each do |op|
214
+ define_action_method([op]) do |command_arguments:, **|
215
+ execute_ascmd(op, command_arguments){Result::Success.new}
180
216
  end
181
217
  end
182
218
 
183
- # actions without ascmd
184
- BASE_ACTIONS = %i[health].concat(TRANSFER_COMMANDS).freeze
185
- # all actions
186
- ACTIONS = [BASE_ACTIONS, AsCmd::OPERATIONS, ASCMD_ALIASES.keys].flatten.freeze
219
+ %i[du md5sum info].each do |op|
220
+ define_action_method([op]) do |command_arguments:, **|
221
+ execute_ascmd(op, command_arguments){ |r| Result::SingleObject.new(r.stringify_keys)}
222
+ end
223
+ end
224
+
225
+ # --- conditions ---
226
+
227
+ def ascmd_available?
228
+ !@ascmd_executor.nil?
229
+ end
230
+
231
+ # --- setup ---
187
232
 
188
- def execute_action
189
- server_transfer_spec = options_to_base_transfer_spec
190
- ascmd_executor = case @connection_type
233
+ # Build the transfer spec and ascmd executor from CLI options.
234
+ # Stores results in instance variables; returns empty context hash.
235
+ def setup_server(**)
236
+ @server_transfer_spec = options_to_base_transfer_spec
237
+ @ascmd_executor = case @connection_type
191
238
  when :local then LocalExecutor.new
192
- when :wss then nil
193
- when :ssh then Ssh.new(server_transfer_spec['remote_host'], server_transfer_spec['remote_user'], @ssh_opts)
239
+ when :wss then nil
240
+ when :ssh then Ssh.new(@server_transfer_spec['remote_host'], @server_transfer_spec['remote_user'], @ssh_opts)
194
241
  else Aspera.error_unexpected_value(@connection_type){'connection type'}
195
242
  end
196
- # the set of available commands depends on SSH executor availability (i.e. no WSS)
197
- available_commands = ascmd_executor.nil? ? BASE_ACTIONS : ACTIONS
198
- # get command and translate aliases
199
- command = options.get_next_command(available_commands)
200
- command = ASCMD_ALIASES[command] if ASCMD_ALIASES.key?(command)
201
- case command
202
- when :health
203
- nagios = Nagios.new
204
- command_nagios = options.get_next_command(%i[transfer])
205
- case command_nagios
206
- when :transfer
207
- probe_ts = server_transfer_spec.merge({
208
- 'direction' => 'send',
209
- 'cookie' => 'aspera.sync', # hide in console
210
- 'resume_policy' => 'none',
211
- 'paths' => [{'source' => 'faux:///pingfile?1k', 'destination' => '.fasping'}]
212
- })
213
- statuses = transfer.start(probe_ts)
214
- if TransferAgent.session_status(statuses).eql?(:success)
215
- nagios.add_ok('transfer', 'ok')
216
- else
217
- nagios.add_critical('transfer', statuses.reject{ |i| i.eql?(:success)}.first.to_s)
218
- end
219
- else Aspera.error_unexpected_value(command_nagios)
220
- end
221
- Main.result_object_list(nagios.status_list)
222
- when *TRANSFER_COMMANDS
223
- return execute_transfer(command, server_transfer_spec)
224
- when *AsCmd::OPERATIONS
225
- command_arguments = options.get_next_argument('ascmd command arguments', multiple: true, mandatory: false)
226
- ascmd = AsCmd.new(ascmd_executor)
227
- begin
228
- result = ascmd.execute_single(command, command_arguments)
229
- case command
230
- when :mkdir, :mv, :cp, :rm
231
- return Main.result_success
232
- when :ls
233
- return Main.result_object_list(result.map(&:stringify_keys), fields: %w[zmode zuid zgid size mtime name])
234
- when :df
235
- return Main.result_object_list(result.map(&:stringify_keys))
236
- when :du, :md5sum, :info
237
- return Main.result_single_object(result.stringify_keys)
238
- end
239
- rescue AsCmd::Error => e
240
- raise Cli::BadArgument, e.extended_message
241
- end
242
- else Aspera.error_unreachable_line
243
+ {}
244
+ end
245
+
246
+ # --- health ---
247
+
248
+ def action_health_transfer
249
+ nagios = Nagios.new
250
+ probe_ts = @server_transfer_spec.merge({
251
+ 'direction' => 'send',
252
+ 'cookie' => 'aspera.sync', # hide in console
253
+ 'resume_policy' => 'none',
254
+ 'paths' => [{'source' => 'faux:///pingfile?1k', 'destination' => '.fasping'}]
255
+ })
256
+ statuses = transfer.start(probe_ts)
257
+ if statuses.is_a?(Transfer::Result::Success)
258
+ nagios.add_ok('transfer', 'ok')
259
+ else
260
+ nagios.add_critical('transfer', statuses.is_a?(Transfer::Result::Error) ? statuses.exception.message : statuses.to_s)
261
+ end
262
+ Result::ObjectList.new(nagios.status_list)
263
+ end
264
+
265
+ # --- ascmd handlers ---
266
+
267
+ def action_ls(command_arguments:, **)
268
+ execute_ascmd(:ls, command_arguments) do |result|
269
+ Result::ObjectList.new(result.map(&:stringify_keys), fields: %w[zmode zuid zgid size mtime name])
270
+ end
271
+ end
272
+
273
+ def action_df(command_arguments:, **)
274
+ execute_ascmd(:df, command_arguments) do |result|
275
+ Result::ObjectList.new(result.map(&:stringify_keys))
276
+ end
277
+ end
278
+
279
+ private
280
+
281
+ def execute_ascmd(op, command_arguments)
282
+ ascmd = AsCmd.new(@ascmd_executor)
283
+ begin
284
+ result = ascmd.execute_single(op, command_arguments)
285
+ yield(result)
286
+ rescue AsCmd::Error => e
287
+ raise Cli::BadArgument, e.extended_message
243
288
  end
244
289
  end
245
290
  end