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
@@ -1,480 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'aspera/cli/manager'
4
- require 'aspera/cli/formatter'
5
- require 'aspera/cli/plugins/config'
6
- require 'aspera/cli/extended_value'
7
- require 'aspera/cli/plugins/factory'
8
- require 'aspera/cli/transfer_agent'
9
- require 'aspera/cli/version'
10
- require 'aspera/cli/info'
11
- require 'aspera/cli/hints'
12
- require 'aspera/secret_hider'
13
- require 'aspera/log'
14
- require 'aspera/assert'
15
- require 'aspera/schema/documentation'
16
- require 'aspera/schema/registry'
17
- require 'net/ssh/errors'
18
- require 'openssl'
19
-
20
- module Aspera
21
- module Cli
22
- # Global objects shared with plugins
23
- class Context
24
- # @type [Array<Symbol>]
25
- MEMBERS = %i[options transfer config formatter persistency man_header].freeze
26
- # @!attribute [rw] options
27
- # @return [Manager] the command line options manager
28
- # @!attribute [rw] transfer
29
- # @return [TransferAgent] the transfer agent, used by transfer plugins
30
- # @!attribute [rw] config
31
- # @return [Plugins::Config] the configuration plugin, used by plugins to get configuration values and presets
32
- # @!attribute [rw] formatter
33
- # @return [Formatter] the formatter, used by plugins to display results and messages
34
- # @!attribute [rw] persistency
35
- # @return [Object] # whatever the type is
36
- # @!attribute [rw] man_header
37
- # @return [Boolean] whether to display the manual header in plugin help
38
- attr_accessor(*MEMBERS)
39
-
40
- # Initialize all members to nil, so that they are defined and can be validated later
41
- # @return [nil]
42
- def initialize
43
- MEMBERS.each{ |i| instance_variable_set(:"@#{i}", nil)}
44
- end
45
-
46
- # Validate that all members are set, raise exception if not
47
- # @raise [Aspera::AssertionError] if any member is not set
48
- # @return [nil]
49
- def validate
50
- MEMBERS.each do |i|
51
- Aspera.assert(instance_variable_defined?(:"@#{i}"))
52
- Aspera.assert(!instance_variable_get(:"@#{i}").nil?)
53
- end
54
- end
55
-
56
- # Check if the context is in manual-only mode
57
- # @return [Boolean] true if in manual-only mode
58
- def only_manual?
59
- transfer.eql?(:only_manual)
60
- end
61
-
62
- # Set the context to manual-only mode
63
- # @return [Symbol] :only_manual
64
- def only_manual!
65
- @transfer = :only_manual
66
- end
67
- end
68
-
69
- # The main CLI class
70
- class Main
71
- # Plugins store transfer result using this key and use result_transfer_multiple()
72
- STATUS_FIELD = 'status'
73
- COMMAND_CONFIG = :config
74
- COMMAND_HELP = :help
75
- # Types that go to result of type = text
76
- SCALAR_TYPES = [String, Integer, Symbol].freeze
77
- USER_INTERFACES = %i[text graphical].freeze
78
-
79
- private_constant :COMMAND_CONFIG, :COMMAND_HELP, :SCALAR_TYPES, :USER_INTERFACES
80
-
81
- class << self
82
- # Create a special result type (only used internally here)
83
- # @param special_sym [Symbol] the special result type
84
- # @return [Hash] result hash with type :special
85
- def result_special(special_sym); {type: :special, data: special_sym}; end
86
-
87
- # Expect some list, but nothing to display
88
- # @return [Hash] result hash for empty list
89
- def result_empty; result_special(:empty); end
90
-
91
- # Nothing expected
92
- # @return [Hash] result hash for nothing
93
- def result_nothing; result_special(:nothing); end
94
-
95
- # Result is some status, such as "complete", "deleted"...
96
- # @param status [String] The status
97
- # @return [Hash] result hash with type :status
98
- def result_status(status); return {type: :status, data: status}; end
99
-
100
- # Text result coming from command result
101
- # @param data [String, Integer, Symbol] the text data to display
102
- # @return [Hash] result hash with type :text
103
- def result_text(data); return {type: :text, data: data}; end
104
-
105
- # Create a success result
106
- # @return [Hash] result hash with status 'complete'
107
- def result_success; return result_status('complete'); end
108
-
109
- # Process statuses of finished transfer sessions
110
- # @param statuses [Array] array of transfer session statuses
111
- # @raise [Symbol] exception if there is one error
112
- # @return [Hash] empty status result if all transfers succeeded
113
- def result_transfer(statuses)
114
- worst = TransferAgent.session_status(statuses)
115
- raise worst unless worst.eql?(:success)
116
- return Main.result_nothing
117
- end
118
-
119
- # Used when one command executes several transfer jobs (each job being possibly multi session)
120
- # @param status_table [Array] [{STATUS_FIELD=>[status array],...},...]
121
- # @return a status object suitable as command result
122
- # Each element has a key STATUS_FIELD which contains the result of possibly multiple sessions
123
- def result_transfer_multiple(status_table)
124
- global_status = :success
125
- # Transform status array into string and find if there was problem
126
- status_table.each do |item|
127
- worst = TransferAgent.session_status(item[STATUS_FIELD])
128
- global_status = worst unless worst.eql?(:success)
129
- item[STATUS_FIELD] = item[STATUS_FIELD].join(',')
130
- end
131
- raise global_status unless global_status.eql?(:success)
132
- return result_object_list(status_table)
133
- end
134
-
135
- # Display image for that URL or directly blob
136
- # @param url_or_blob [String] URL or blob to display as image
137
- # @return [Hash] result hash with type :image
138
- def result_image(url_or_blob)
139
- return {type: :image, data: url_or_blob}
140
- end
141
-
142
- # A single object, must be Hash
143
- # @param data [Hash] the object data
144
- # @param fields [Array<String>, nil] optional list of fields to display
145
- # @return [Hash] result hash with type :single_object
146
- def result_single_object(data, fields: nil)
147
- return {type: :single_object, data: data, fields: fields}
148
- end
149
-
150
- # An Array of Hash
151
- # @param data [Array<Hash>] array of objects
152
- # @param fields [Array<String>, nil] optional list of fields to display
153
- # @param total [Integer, nil] optional total count
154
- # @return [Hash] result hash with type :object_list
155
- def result_object_list(data, fields: nil, total: nil)
156
- return {type: :object_list, data: data, fields: fields, total: total}
157
- end
158
-
159
- # A list of values
160
- # @param data [Array] The list of values
161
- # @param name [String] The name of the list (used for display)
162
- # @return [Hash] result hash with type :value_list
163
- def result_value_list(data, name: 'id')
164
- Aspera.assert_type(data, Array)
165
- Aspera.assert_type(name, String)
166
- return {type: :value_list, data: data, name: name}
167
- end
168
-
169
- # Determines type of result based on data
170
- # @param data [Object] the data to analyze and format
171
- # @return [Hash] result hash with appropriate type based on data
172
- def result_auto(data)
173
- case data
174
- when NilClass
175
- return result_special(:null)
176
- when Hash
177
- return result_single_object(data)
178
- when Array
179
- all_types = data.map(&:class).uniq
180
- return result_object_list(data) if all_types.eql?([Hash])
181
- unsupported_types = all_types - SCALAR_TYPES
182
- return result_value_list(data, name: 'list') if unsupported_types.empty?
183
- Aspera.error_unexpected_value(unsupported_types){'list item types'}
184
- when *SCALAR_TYPES
185
- return result_text(data)
186
- else Aspera.error_unexpected_value(data.class.name){'result type'}
187
- end
188
- end
189
- end
190
-
191
- # Minimum initialization, no exception raised
192
- # @param argv [Array<String>] command line arguments
193
- # @return [nil]
194
- def initialize(argv)
195
- @argv = argv
196
- Log.dump(:argv, @argv, level: :trace2)
197
- @option_help = false
198
- @option_show_config = false
199
- @bash_completion = false
200
- @context = Context.new
201
- end
202
-
203
- # This is the main function called by initial script just after constructor
204
- # Processes command line arguments, executes commands, and handles exceptions
205
- # @return [nil]
206
- def process_command_line
207
- # Catch exception information , if any
208
- exception_info = nil
209
- # False if command shall not be executed (e.g. --show-config)
210
- execute_command = true
211
- # Catch exceptions
212
- begin
213
- init_agents_and_options
214
- # Find plugins, shall be after parse! ?
215
- Plugins::Factory.instance.add_plugins_from_lookup_folders
216
- # Help requested without command ? (plugins must be known here)
217
- show_usage if @option_help && @context.options.command_or_arg_empty?
218
- generate_bash_completion if @bash_completion
219
- @context.config.periodic_check_newer_gem_version
220
- command_sym =
221
- if @option_show_config && @context.options.command_or_arg_empty?
222
- COMMAND_CONFIG
223
- else
224
- @context.options.get_next_command(Plugins::Factory.instance.plugin_list.unshift(COMMAND_HELP))
225
- end
226
- # Command will not be executed, but we need manual
227
- @context.options.fail_on_missing_mandatory = false if @option_help || @option_show_config
228
- # Main plugin is not dynamically instantiated
229
- case command_sym
230
- when COMMAND_HELP
231
- show_usage
232
- when COMMAND_CONFIG
233
- command_plugin = @context.config
234
- else
235
- # Get plugin, set options, etc
236
- command_plugin = get_plugin_instance_with_options(command_sym)
237
- # Parse plugin specific options
238
- @context.options.parse_options!
239
- end
240
- # Help requested for current plugin
241
- show_usage(all: false) if @option_help
242
- if @option_show_config
243
- @context.formatter.display_results(type: :single_object, data: @context.options.known_options(only_defined: true).stringify_keys)
244
- execute_command = false
245
- end
246
- # Locking for single execution (only after "per plugin" option, in case lock port is there)
247
- lock_port = @context.options.get_option(:lock_port)
248
- if !lock_port.nil?
249
- begin
250
- # No need to close later, will be freed on process exit. must save in member else it is garbage collected
251
- Log.log.debug{"Opening lock port #{lock_port}"}
252
- # Loopback address, could also be 'localhost'
253
- @tcp_server = TCPServer.new('127.0.0.1', lock_port)
254
- rescue StandardError => e
255
- execute_command = false
256
- Log.log.warn{"Another instance is already running (#{e.message})."}
257
- end
258
- end
259
- pid_file = @context.options.get_option(:pid_file)
260
- if !pid_file.nil?
261
- File.write(pid_file, Process.pid)
262
- Log.log.debug{"Wrote pid #{Process.pid} to #{pid_file}"}
263
- at_exit{File.delete(pid_file)}
264
- end
265
- # Execute and display (if not exclusive execution)
266
- @context.formatter.display_results(**command_plugin.execute_action) if execute_command
267
- # Save config file if command modified it
268
- @context.config.save_config_file_if_needed
269
- # Finish
270
- @context.transfer.shutdown
271
- rescue Net::SSH::AuthenticationFailed => e; exception_info = {e: e, t: 'SSH', security: true}
272
- rescue OpenSSL::SSL::SSLError => e; exception_info = {e: e, t: 'SSL'}
273
- rescue Cli::BadArgument => e; exception_info = {e: e, t: 'Argument', usage: true}
274
- rescue Cli::MissingArgument => e; exception_info = {e: e, t: 'Missing', usage: true}
275
- rescue Cli::BadIdentifier => e; exception_info = {e: e, t: 'Identifier'}
276
- rescue Cli::SchemaRequest => e; exception_info = {e: e, t: 'Schema'}
277
- rescue Cli::Error => e; exception_info = {e: e, t: 'Tool', usage: true}
278
- rescue Transfer::Error => e; exception_info = {e: e, t: 'Transfer'}
279
- rescue RestCallError => e; exception_info = {e: e, t: 'Rest'}
280
- rescue SocketError => e; exception_info = {e: e, t: 'Network'}
281
- rescue StandardError => e; exception_info = {e: e, t: "Other(#{e.class.name})", debug: true}
282
- rescue Interrupt => e; exception_info = {e: e, t: 'Interruption', debug: true}
283
- end
284
- # Cleanup file list files
285
- TempFileManager.instance.cleanup
286
- # 1- processing of error condition
287
- unless exception_info.nil?
288
- Log.log.warn(exception_info[:e].message) if Log.instance.logger_type.eql?(:syslog) && exception_info[:security]
289
- Log.log.error{"#{exception_info[:t]}: #{exception_info[:e].message}"} unless exception_info[:e].is_a?(Cli::SchemaRequest)
290
- Log.log.debug{(['Backtrace:'] + exception_info[:e].backtrace).join("\n")} if exception_info[:debug]
291
- @context.formatter.display_message(:error, 'Use option -h to get help.') if exception_info[:usage]
292
- # Is that a known error condition with proposal for remediation ?
293
- Hints.hint_for(exception_info[:e], @context.formatter)
294
- # Requested help for a Hash parameter/option ?
295
- if exception_info[:e].is_a?(Cli::SchemaRequest)
296
- Log.log.info{"#{exception_info[:t]}: #{exception_info[:e].message}"}
297
- schema_path = exception_info[:e].path
298
- if schema_path.nil?
299
- Log.log.warn{'Sorry, no schema provided yet. Please refer to the manual or API.'}
300
- else
301
- builder = Schema::Documentation.new(TerminalFormatter, Schema::Registry.instance.reader(schema_path)).build
302
- @context.formatter.display_results(**Main.result_object_list(builder.rows, fields: builder.columns))
303
- end
304
- end
305
- end
306
- # 2- processing of command not processed (due to exception or bad command line)
307
- if execute_command || @option_show_config
308
- @context.options.final_errors.each do |msg|
309
- Log.log.error{"Argument: #{msg}"}
310
- # Add code as exception if there is not already an error
311
- exception_info = {e: Exception.new(msg), t: 'UnusedArg'} if exception_info.nil?
312
- end
313
- end
314
- # 3- in case of error, fail the process status
315
- unless exception_info.nil?
316
- # Show stack trace in debug mode
317
- raise exception_info[:e] if Log.log.debug?
318
- # Else give hint and exit
319
- @context.formatter.display_message(:error, 'Use --log-level=debug to get more details.') if exception_info[:debug]
320
- Process.exit(1)
321
- end
322
- return
323
- end
324
-
325
- # Display usage information and help
326
- # @param all [Boolean] if true, show help for all plugins; if false, show only current plugin
327
- # @param exit [Boolean] if true, exit the process after displaying help
328
- # @return [nil]
329
- def show_usage(all: true, exit: true)
330
- # Display main plugin options (+config)
331
- @context.formatter.display_message(:error, @context.options.parser)
332
- if all
333
- @context.only_manual!
334
- # List plugins that have a "require" field, i.e. all but main plugin
335
- Plugins::Factory.instance.plugin_list.each do |plugin_name_sym|
336
- # Config was already included in the global options
337
- next if plugin_name_sym.eql?(COMMAND_CONFIG)
338
- # Override main option parser with a brand new, to avoid having global options
339
- @context.options = Manager.new(Info::CMD_NAME)
340
- @context.options.parser.banner = '' # Remove default banner
341
- get_plugin_instance_with_options(plugin_name_sym)
342
- # Display generated help for plugin options
343
- @context.formatter.display_message(:error, @context.options.parser.help)
344
- end
345
- end
346
- Process.exit(0) if exit
347
- end
348
-
349
- private
350
-
351
- # Initialize agents and options
352
- # This can throw exception if there is a problem with the environment, needs to be caught by execute method
353
- # @raise [StandardError] if there is a problem with the environment
354
- # @return [nil]
355
- def init_agents_and_options
356
- @context.man_header = true
357
- # Create formatter, in case there is an exception, it is used to display.
358
- @context.formatter = Formatter.new
359
- # Create command line manager with arguments
360
- @context.options = Manager.new(Info::CMD_NAME, @argv)
361
- # Formatter adds options
362
- @context.formatter.declare_options(@context.options)
363
- # Compare $0 with expected name
364
- current_prog_name = File.basename($PROGRAM_NAME)
365
- Aspera.assert(current_prog_name.eql?(Info::CMD_NAME), type: :warn){"Please use '#{Info::CMD_NAME}' instead of '#{current_prog_name}'"}
366
- # Declare and parse global options
367
- declare_global_options
368
- # Do not display config commands if help is asked
369
- @context.man_header = false
370
- # The Config plugin adds the @preset parser, so declare before TransferAgent which may use it
371
- @context.config = Plugins::Config.new(context: @context)
372
- @context.man_header = true
373
- # Data persistency is set in config
374
- Aspera.assert(@context.persistency){'missing persistency object'}
375
- # The TransferAgent plugin may use the @preset parser
376
- @context.transfer = TransferAgent.new(@context.options, @context.config)
377
- # Add commands for config plugin after all options have been added
378
- @context.config.add_manual_header(false)
379
- @context.validate
380
- # Set banner when all environment is created so that additional extended value modifiers are known, e.g. @preset
381
- @context.options.parser.banner = app_banner
382
- end
383
-
384
- # Generate the application banner for help display
385
- # @return [String] formatted banner text
386
- def app_banner
387
- t = ' ' * 8
388
- return <<~END_OF_BANNER
389
- NAME
390
- #{t}#{Info::CMD_NAME} -- a command line tool for Aspera Applications (v#{Cli::VERSION})
391
-
392
- SYNOPSIS
393
- #{t}#{Info::CMD_NAME} COMMANDS [OPTIONS] [ARGS]
394
-
395
- DESCRIPTION
396
- #{t}Use Aspera application to perform operations on command line.
397
- #{t}Documentation and examples: #{Info::GEM_URL}
398
- #{t}execute: #{Info::CMD_NAME} conf doc
399
- #{t}or visit: #{Info::DOC_URL}
400
- #{t}source repo: #{Info::SRC_URL}
401
-
402
- ENVIRONMENT VARIABLES
403
- #{t}Any option can be set as an environment variable, refer to the manual
404
-
405
- COMMANDS
406
- #{t}To list first level commands, execute: #{Info::CMD_NAME}
407
- #{t}Note that commands can be written shortened (provided it is unique).
408
-
409
- OPTIONS
410
- #{t}Options begin with a '-' (minus), and value is provided on command line.
411
- #{t}Special values are supported beginning with special prefix @pfx:, where pfx is one of:
412
- #{t}#{ExtendedValue.instance.modifiers.join(', ')}
413
- #{t}Dates format is 'DD-MM-YY HH:MM:SS', or 'now' or '-<num>h'
414
-
415
- ARGS
416
- #{t}Some commands require mandatory arguments, e.g. a path.
417
- END_OF_BANNER
418
- end
419
-
420
- # Define header for manual and declare all global options
421
- # @return [nil]
422
- def declare_global_options
423
- Log.log.debug('declare_global_options')
424
- @context.options.declare(:help, 'Show this message', allowed: Allowed::TYPES_NONE, short: 'h'){@option_help = true}
425
- @context.options.declare(:bash_comp, 'Generate bash completion for command', allowed: Allowed::TYPES_NONE){@bash_completion = true}
426
- @context.options.declare(:show_config, 'Display parameters used for the provided action', allowed: Allowed::TYPES_NONE){@option_show_config = true}
427
- @context.options.declare(:version, 'Display version', allowed: Allowed::TYPES_NONE, short: 'v'){@context.formatter.display_message(:data, Cli::VERSION); Process.exit(0)} # rubocop:disable Style/Semicolon
428
- @context.options.declare(
429
- :ui, 'Method to start browser',
430
- allowed: USER_INTERFACES,
431
- handler: {o: Environment.instance, m: :url_method}
432
- )
433
- @context.options.declare(
434
- :invalid_characters, 'Replacement character and invalid filename characters',
435
- handler: {o: Environment.instance, m: :file_illegal_characters}
436
- )
437
- @context.options.declare(:log_level, 'Log level', allowed: Log::LEVELS, handler: {o: Log.instance, m: :level})
438
- @context.options.declare(:log_format, 'Log formatter', allowed: [Proc, Logger::Formatter, String], handler: {o: Log.instance, m: :formatter})
439
- @context.options.declare(:logger, 'Logging method', allowed: Log::LOG_TYPES, handler: {o: Log.instance, m: :logger_type})
440
- @context.options.declare(:lock_port, 'Prevent dual execution of a command, e.g. in cron', allowed: Allowed::TYPES_INTEGER)
441
- @context.options.declare(:once_only, 'Process only new items (some commands)', allowed: Allowed::TYPES_BOOLEAN, default: false)
442
- @context.options.declare(:log_secrets, 'Show passwords in logs', allowed: Allowed::TYPES_BOOLEAN, handler: {o: SecretHider.instance, m: :log_secrets})
443
- @context.options.declare(:clean_temp, 'Cleanup temporary files on exit', allowed: Allowed::TYPES_BOOLEAN, handler: {o: TempFileManager.instance, m: :cleanup_on_exit})
444
- @context.options.declare(:temp_folder, 'Temporary folder', handler: {o: TempFileManager.instance, m: :global_temp})
445
- @context.options.declare(:pid_file, 'Write process identifier to file, delete on exit')
446
- @context.options.declare(
447
- :parser, 'Default parser for structured parameters and options',
448
- handler: {o: ExtendedValue.instance, m: :default_decoder},
449
- allowed: ExtendedValue::DEFAULT_DECODERS,
450
- default: ExtendedValue::DEFAULT_DECODERS.first
451
- )
452
- # Parse declared options
453
- @context.options.parse_options!
454
- end
455
-
456
- # Get the plugin instance based on name
457
- # Also loads the plugin options, and default values from conf file
458
- # @param plugin_name_sym [Symbol] symbol for plugin name
459
- # @return [Plugins::Base] the plugin instance
460
- def get_plugin_instance_with_options(plugin_name_sym)
461
- Log.log.debug{"get_plugin_instance_with_options(#{plugin_name_sym})"}
462
- # Load default params only if no param already loaded before plugin instantiation
463
- @context.config.add_plugin_default_preset(plugin_name_sym)
464
- command_plugin = Plugins::Factory.instance.create(plugin_name_sym, context: @context)
465
- return command_plugin
466
- end
467
-
468
- # Generate bash completion suggestions
469
- # @return [nil]
470
- def generate_bash_completion
471
- if @context.options.get_next_argument('', multiple: true, mandatory: false).nil?
472
- Plugins::Factory.instance.plugin_list.each{ |p| puts p}
473
- else
474
- Log.log.warn('only first level completion so far')
475
- end
476
- Process.exit(0)
477
- end
478
- end
479
- end
480
- end
@@ -1,51 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # cspell:ignore blankslate
4
-
5
- require 'aspera/rest_error_analyzer'
6
- require 'aspera/assert'
7
- require 'blankslate'
8
-
9
- Aspera::RestErrorAnalyzer.instance.add_simple_handler(name: 'JSON RPC', path: %w[error message], always: true)
10
-
11
- module Aspera
12
- # a very simple JSON RPC client
13
- class JsonRpcClient < BlankSlate
14
- JSON_RPC_VERSION = '2.0'
15
- reveal :instance_variable_get
16
- reveal :inspect
17
- reveal :to_s
18
-
19
- def initialize(api, namespace = nil)
20
- super()
21
- @api = api
22
- @namespace = namespace
23
- @request_id = 0
24
- end
25
-
26
- def respond_to_missing?(_sym, _include_private = false)
27
- true
28
- end
29
-
30
- def method_missing(method, *args, &block)
31
- args = args.first if args.size == 1 && args.first.is_a?(Hash)
32
- data = @api.create('', {
33
- jsonrpc: JSON_RPC_VERSION,
34
- method: "#{@namespace}#{method}",
35
- params: args,
36
- id: @request_id += 1
37
- })
38
- Aspera.assert_type(data, Hash){'response'}
39
- Aspera.assert(data['jsonrpc'] == JSON_RPC_VERSION){'bad version in response'}
40
- Aspera.assert(data.key?('id')){'missing id in response'}
41
- Aspera.assert(!(data.key?('error') && data.key?('result'))){'both error and response'}
42
- Aspera.assert(
43
- !data.key?('error') ||
44
- data['error'].is_a?(Hash) &&
45
- data['error']['code'].is_a?(Integer) &&
46
- data['error']['message'].is_a?(String)
47
- ){'bad error response'}
48
- return data['result']
49
- end
50
- end
51
- end