aspera-cli 4.27.1 → 4.27.3

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 (133) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +67 -1
  4. data/bin/ascli +2 -1
  5. data/docs/README.md +805 -747
  6. data/lib/aspera/agent/connect.rb +6 -4
  7. data/lib/aspera/agent/desktop.rb +2 -2
  8. data/lib/aspera/agent/direct.rb +3 -1
  9. data/lib/aspera/agent/node.rb +3 -3
  10. data/lib/aspera/api/alee.rb +1 -1
  11. data/lib/aspera/api/aoc.rb +14 -12
  12. data/lib/aspera/api/ats.rb +1 -1
  13. data/lib/aspera/api/cos_node.rb +2 -2
  14. data/lib/aspera/api/faspex.rb +9 -7
  15. data/lib/aspera/api/httpgw.rb +37 -33
  16. data/lib/aspera/api/node.rb +38 -33
  17. data/lib/aspera/ascmd.rb +3 -1
  18. data/lib/aspera/ascp/installation.rb +62 -27
  19. data/lib/aspera/ascp/management.rb +1 -0
  20. data/lib/aspera/assert.rb +4 -0
  21. data/lib/aspera/cli/ascp_actions.rb +20 -41
  22. data/lib/aspera/cli/async_transfer_store.rb +2 -2
  23. data/lib/aspera/cli/bootstrapper.rb +11 -15
  24. data/lib/aspera/cli/command_line.rb +252 -0
  25. data/lib/aspera/cli/command_registry.rb +149 -33
  26. data/lib/aspera/cli/command_spec.rb +103 -14
  27. data/lib/aspera/cli/completion/ascli.bash +12 -0
  28. data/lib/aspera/cli/completion/ascli.fish +16 -0
  29. data/lib/aspera/cli/completion/ascli.zsh +19 -0
  30. data/lib/aspera/cli/context.rb +3 -0
  31. data/lib/aspera/cli/deprecation.rb +37 -0
  32. data/lib/aspera/cli/extended_value.rb +2 -0
  33. data/lib/aspera/cli/formatter.rb +87 -75
  34. data/lib/aspera/cli/gem_checker.rb +1 -1
  35. data/lib/aspera/cli/hints.rb +7 -6
  36. data/lib/aspera/cli/http.rb +21 -21
  37. data/lib/aspera/cli/info.rb +3 -0
  38. data/lib/aspera/cli/mcp_tool.rb +47 -83
  39. data/lib/aspera/cli/option_declarator.rb +33 -42
  40. data/lib/aspera/cli/option_registry.rb +69 -0
  41. data/lib/aspera/cli/option_types.rb +103 -0
  42. data/lib/aspera/cli/option_value.rb +281 -0
  43. data/lib/aspera/cli/options.schema.yaml +38 -5
  44. data/lib/aspera/cli/parser.rb +307 -848
  45. data/lib/aspera/cli/plugins/alee.rb +7 -4
  46. data/lib/aspera/cli/plugins/aoc.rb +435 -380
  47. data/lib/aspera/cli/plugins/ats.rb +58 -73
  48. data/lib/aspera/cli/plugins/base.rb +190 -240
  49. data/lib/aspera/cli/plugins/basic_auth.rb +2 -10
  50. data/lib/aspera/cli/plugins/config.rb +244 -178
  51. data/lib/aspera/cli/plugins/console.rb +102 -38
  52. data/lib/aspera/cli/plugins/cos.rb +6 -23
  53. data/lib/aspera/cli/plugins/factory.rb +3 -0
  54. data/lib/aspera/cli/plugins/faspex5.rb +176 -173
  55. data/lib/aspera/cli/plugins/faspio.rb +5 -10
  56. data/lib/aspera/cli/plugins/httpgw.rb +8 -11
  57. data/lib/aspera/cli/plugins/mcp.rb +20 -55
  58. data/lib/aspera/cli/plugins/node.rb +277 -311
  59. data/lib/aspera/cli/plugins/orchestrator.rb +90 -77
  60. data/lib/aspera/cli/plugins/preview.rb +79 -90
  61. data/lib/aspera/cli/plugins/server.rb +76 -50
  62. data/lib/aspera/cli/plugins/shares.rb +68 -116
  63. data/lib/aspera/cli/preset_actions.rb +17 -10
  64. data/lib/aspera/cli/preset_manager.rb +12 -2
  65. data/lib/aspera/cli/prompt.rb +35 -0
  66. data/lib/aspera/cli/result.rb +13 -18
  67. data/lib/aspera/cli/runner.rb +31 -54
  68. data/lib/aspera/cli/special_values.rb +5 -0
  69. data/lib/aspera/cli/sync_actions.rb +41 -37
  70. data/lib/aspera/cli/terminal_formatter.rb +9 -3
  71. data/lib/aspera/cli/transfer_actions.rb +0 -6
  72. data/lib/aspera/cli/transfer_agent.rb +29 -35
  73. data/lib/aspera/cli/vault_manager.rb +0 -17
  74. data/lib/aspera/cli/version.rb +1 -1
  75. data/lib/aspera/cli/wizard.rb +4 -2
  76. data/lib/aspera/command_line_builder.rb +1 -0
  77. data/lib/aspera/coverage.rb +1 -0
  78. data/lib/aspera/environment.rb +7 -1
  79. data/lib/aspera/faspex_gw.rb +2 -1
  80. data/lib/aspera/faspex_postproc.rb +1 -0
  81. data/lib/aspera/graphql.rb +5 -5
  82. data/lib/aspera/json_rpc/client.rb +5 -5
  83. data/lib/aspera/keychain/encrypted_hash.rb +1 -1
  84. data/lib/aspera/keychain/one_password_api.rb +1 -1
  85. data/lib/aspera/link_header.rb +2 -2
  86. data/lib/aspera/log.rb +22 -25
  87. data/lib/aspera/markdown.rb +2 -0
  88. data/lib/aspera/mime.rb +25 -0
  89. data/lib/aspera/node_simulator.rb +1 -0
  90. data/lib/aspera/oauth/base.rb +35 -25
  91. data/lib/aspera/oauth/factory.rb +1 -0
  92. data/lib/aspera/oauth/generic.rb +1 -1
  93. data/lib/aspera/oauth/jwt.rb +1 -1
  94. data/lib/aspera/oauth/web.rb +9 -8
  95. data/lib/aspera/preview/file_types.rb +4 -4
  96. data/lib/aspera/preview/generator.rb +7 -0
  97. data/lib/aspera/preview/options.rb +4 -4
  98. data/lib/aspera/preview/terminal.rb +4 -3
  99. data/lib/aspera/preview/utils.rb +9 -6
  100. data/lib/aspera/products/connect.rb +1 -1
  101. data/lib/aspera/rainbow.rb +7 -0
  102. data/lib/aspera/rest/aspera_errors.rb +60 -0
  103. data/lib/aspera/rest/call_error.rb +27 -0
  104. data/lib/aspera/rest/client.rb +514 -0
  105. data/lib/aspera/rest/error_analyzer.rb +113 -0
  106. data/lib/aspera/rest/list.rb +143 -0
  107. data/lib/aspera/rest/parameters.rb +55 -0
  108. data/lib/aspera/rest/util.rb +176 -0
  109. data/lib/aspera/rest.rb +7 -621
  110. data/lib/aspera/schema/IBM Aspera Console-enhanced.yaml +1125 -0
  111. data/lib/aspera/schema/IBM Aspera on Cloud API-0.2.6-enhanced.yaml +39 -0
  112. data/lib/aspera/schema/IBM Aspera on Cloud Automation API-1.0.5-enhanced.yaml +2395 -0
  113. data/lib/aspera/schema/documentation.rb +13 -3
  114. data/lib/aspera/schema/registry.rb +18 -1
  115. data/lib/aspera/schema/validator.rb +92 -0
  116. data/lib/aspera/secret_hider.rb +36 -25
  117. data/lib/aspera/string_ext.rb +15 -0
  118. data/lib/aspera/temp_file_manager.rb +6 -5
  119. data/lib/aspera/transfer/parameters.rb +2 -0
  120. data/lib/aspera/transfer/spec.rb +1 -0
  121. data/lib/aspera/transfer/spec.schema.yaml +1 -0
  122. data/lib/aspera/uri_reader.rb +11 -11
  123. data/lib/aspera/web_auth/index.html +147 -0
  124. data/lib/aspera/web_auth/server.rb +81 -0
  125. data.tar.gz.sig +0 -0
  126. metadata +39 -7
  127. metadata.gz.sig +0 -0
  128. data/lib/aspera/colors.rb +0 -79
  129. data/lib/aspera/rest_call_error.rb +0 -25
  130. data/lib/aspera/rest_error_analyzer.rb +0 -111
  131. data/lib/aspera/rest_errors_aspera.rb +0 -58
  132. data/lib/aspera/rest_list.rb +0 -136
  133. data/lib/aspera/web_auth.rb +0 -211
@@ -12,7 +12,7 @@ require 'aspera/id_generator'
12
12
  require 'aspera/api/node'
13
13
  require 'aspera/oauth'
14
14
  require 'aspera/node_simulator'
15
- require 'aspera/rest_list'
15
+ require 'aspera/rest/list'
16
16
  require 'aspera/assert'
17
17
  require 'base64'
18
18
  require 'zlib'
@@ -47,7 +47,7 @@ module Aspera
47
47
  error = nil
48
48
  urls.each do |base_url|
49
49
  next unless base_url.match?(%r{^https?://})
50
- api = Rest.new(base_url: base_url)
50
+ api = Rest::Client.new(base_url: base_url)
51
51
  test_endpoint = 'ping'
52
52
  http = api.read(test_endpoint, ret: :resp)
53
53
  next unless http.body.eql?('')
@@ -76,9 +76,9 @@ module Aspera
76
76
  option :validator, description: 'Identifier of validator (optional for central)'
77
77
  option :asperabrowserurl, description: 'URL for simple aspera web ui', default: 'https://asperabrowser.mybluemix.net'
78
78
  option :node_api, description: 'Gen4: standard_ports: Use standard FASP ports (true) or get from node API (false). cache: Set to false to force actual file system read',
79
- allowed: Hash, handler: {o: Api::Node, m: :api_options}
79
+ allowed: Hash, on_set: Api::Node.method(:api_options=)
80
80
  option :root_id, description: 'Gen4: File id of top folder when using access key (override AK root id)'
81
- option :dynamic_key, description: 'Private key PEM to use for dynamic key auth', handler: {o: Api::Node, m: :use_dynamic_key}
81
+ option :dynamic_key, description: 'Private key PEM to use for dynamic key auth', on_set: Api::Node.method(:use_dynamic_key=)
82
82
 
83
83
  # @param wizard [Wizard] The wizard object
84
84
  # @param app_url [String] Tested URL
@@ -106,25 +106,21 @@ module Aspera
106
106
  # Fields removed in result of search
107
107
  SEARCH_REMOVE_FIELDS = %w[basename permissions].freeze
108
108
 
109
- # Actions in execute_command_gen3
110
- COMMANDS_GEN3 = %i[search space mkdir mklink mkfile rename delete ls upload download cat sync transport spec]
111
-
112
- # Shared DSL metadata for Gen3 commands (description:, arguments:, transfer_paths:, aliases:).
113
- # Consumed by commands_under(:files) in shares.rb and commands_under %i[source node]) in faspex.rb.
114
- # :sync is excluded (intermediate node with sub-commands, handled separately).
115
- # action: entries are node-specific and intentionally omitted.
109
+ # DSL metadata for Gen3 root commands (description:, arguments:, transfer_paths:, aliases:).
110
+ # :access_keys is skipped (intermediate node declared separately), :sync is declared separately.
111
+ # action: entries are in GEN3_NODE_ACTIONS, or implicit.
116
112
  COMMANDS_GEN3_SPEC = {
117
- search: {description: 'Search for files', arguments: [{name: :search_root, type: String}]},
118
- space: {description: 'Show space information', arguments: [{name: :path_list, multiple: true}]},
119
- mkdir: {description: 'Create a folder (Gen3)', arguments: [{name: :path_list, multiple: true}]},
120
- mklink: {description: 'Create a symbolic link (Gen3)', arguments: [{name: :target, type: String}, {name: :link_path, type: String}]},
121
- mkfile: {description: 'Create a file (Gen3)', arguments: [{name: :file_path, type: String}, {name: :contents, mandatory: false, default: nil}]},
122
- rename: {description: 'Rename a file or folder (Gen3)', arguments: [{name: :path_base, type: String}, {name: :path_src, type: String}, {name: :path_dst, type: String}]},
113
+ search: {description: 'Search for files', arguments: [{name: :path}]},
114
+ space: {description: 'Show space information', arguments: [{name: :paths, multiple: true}]},
115
+ mkdir: {description: 'Create a folder (Gen3)', arguments: [{name: :paths, multiple: true}]},
116
+ mklink: {description: 'Create a symbolic link (Gen3)', arguments: [{name: :target}, {name: :link_path}]},
117
+ mkfile: {description: 'Create a file (Gen3)', arguments: [{name: :path}, {name: :contents, mandatory: false, default: nil}]},
118
+ rename: {description: 'Rename a file or folder (Gen3)', arguments: [{name: :folder}, {name: :source}, {name: :destination}]},
123
119
  delete: {description: 'Delete files or folders (Gen3)', arguments: [{name: :paths, multiple: true}]},
124
- ls: {description: 'List files (Gen3)', arguments: [{name: :path, type: String}], aliases: [:browse]},
120
+ ls: {description: 'List files (Gen3)', arguments: [{name: :path}], aliases: [:browse]},
125
121
  upload: {description: 'Upload files (Gen3)', transfer_paths: :send},
126
122
  download: {description: 'Download files (Gen3)', transfer_paths: :receive},
127
- cat: {description: 'Show file contents (Gen3)', arguments: [{name: :remote_path, type: String}]},
123
+ cat: {description: 'Show file contents (Gen3)', arguments: [{name: :path}]},
128
124
  transport: {description: 'Show transport parameters'},
129
125
  spec: {description: 'Show transfer spec base'},
130
126
  api_details: {description: 'Show API details'},
@@ -136,60 +132,52 @@ module Aspera
136
132
  access_keys: {description: 'Manage access keys'}
137
133
  }.freeze
138
134
 
139
- BASE_ACTIONS = (%i[api_details] + COMMANDS_GEN3).freeze
140
-
141
- SPECIAL_ACTIONS = %i[health events info slash license].freeze
142
-
143
- # commands for : `execute_simple_common`: actions used commonly when a node is involved
144
- COMMON_ACTIONS = (%i[access_keys] + BASE_ACTIONS + SPECIAL_ACTIONS).freeze
145
-
146
- private_constant :CENTRAL_SOAP_API_TEST, :SEARCH_REMOVE_FIELDS, :BASE_ACTIONS, :SPECIAL_ACTIONS, :COMMON_ACTIONS
135
+ private_constant :CENTRAL_SOAP_API_TEST, :SEARCH_REMOVE_FIELDS
147
136
 
148
- # used in aoc
137
+ # Gen4 read commands also exposed on AoC packages (`aoc packages ls <id>`)
149
138
  NODE4_READ_ACTIONS = %i[bearer_token_node node_info ls find].freeze
150
139
 
151
- # commands for execute_command_gen4
152
- COMMANDS_GEN4 = (%i[mkdir mklink mkfile rename delete upload download sync cat show modify permission thumbnail v3] + NODE4_READ_ACTIONS).freeze
153
-
154
- # Shared DSL metadata for all Gen4 commands (description:, arguments:, transfer_paths:, aliases:).
155
- # Consumed by commands_under %i[access_keys do]) in node.rb and by aoc.rb.
156
- # :sync and :permission are excluded: they are intermediate nodes handled separately.
157
- SINGLE_PATH_ARG = [{name: :path, type: String}].freeze
140
+ # DSL metadata for Gen4 commands under `access_keys do` (description:, arguments:, transfer_paths:, aliases:).
141
+ # Other plugins expose this sub-tree with `mount:` (aoc, ats).
142
+ # :sync, :permission and :v3 are excluded: they are intermediate nodes declared separately.
143
+ SINGLE_PATH_ARG = [{name: :path}].freeze
158
144
  COMMANDS_GEN4_SPEC = {
159
145
  mkdir: {description: 'Create folder', arguments: SINGLE_PATH_ARG},
160
146
  mklink: {description: 'Create symbolic link', arguments: SINGLE_PATH_ARG},
161
- mkfile: {description: 'Create file', arguments: [{name: :path, type: String}, {name: :contents, mandatory: false, default: nil}]},
162
- rename: {description: 'Rename entry', arguments: [{name: :source_path, type: String}, {name: :new_name, type: String}]},
163
- delete: {description: 'Delete entry', arguments: [{name: :paths, type: String, bulk: true}]},
147
+ mkfile: {description: 'Create file', arguments: [{name: :path}, {name: :contents, mandatory: false, default: nil}]},
148
+ rename: {description: 'Rename entry', arguments: [{name: :source_path}, {name: :new_name}]},
149
+ delete: {description: 'Delete entry', arguments: [{name: :path, bulk: true}]},
164
150
  upload: {description: 'Upload files', transfer_paths: :send},
165
151
  download: {description: 'Download files', transfer_paths: :receive},
166
- modify: {description: 'Modify file', arguments: [{name: :path, type: String}, {name: :update_value, type: Hash, schema: 'node:components.schemas.files-id-put-request'}]},
152
+ modify: {description: 'Modify file', arguments: [{name: :path}, {name: :file, type: Hash, schema: 'node:components.schemas.files-id-put-request'}]},
167
153
  cat: {description: 'Show file contents', arguments: SINGLE_PATH_ARG},
168
154
  show: {description: 'Show file info', arguments: SINGLE_PATH_ARG},
169
155
  thumbnail: {description: 'Show file thumbnail', arguments: SINGLE_PATH_ARG},
170
156
  bearer_token_node: {description: 'Show bearer token for file node', arguments: SINGLE_PATH_ARG},
171
157
  node_info: {description: 'Show node info for file', arguments: SINGLE_PATH_ARG},
172
158
  ls: {description: 'List files', arguments: SINGLE_PATH_ARG, aliases: [:browse]},
173
- find: {description: 'Find files', arguments: SINGLE_PATH_ARG + FILTER_ARGS},
174
- v3: {description: 'Legacy v3 commands on files'}
159
+ find: {description: 'Find files', arguments: SINGLE_PATH_ARG + FILTER_ARGS}
175
160
  }.freeze
176
161
  private_constant :SINGLE_PATH_ARG
177
162
 
178
- # commands supported in ATS for COS
163
+ # Root commands exposed by `cos node` and `shares files` (mount: only:)
179
164
  COMMANDS_COS = %i[upload download info access_keys api_details transfer].freeze
180
- COMMANDS_SHARES = (BASE_ACTIONS - %i[search]).freeze
165
+ COMMANDS_SHARES = %i[api_details space mkdir mklink mkfile rename delete ls upload download cat sync transport spec].freeze
181
166
  # `browse` display fields for gen4
182
167
  GEN4_LS_FIELDS = %w[name type recursive_size size modified_time access_level].freeze
183
168
 
184
- # @param api [Rest] an existing API object for the Node API
169
+ # @param api [Rest::Client] an existing API object for the Node API
185
170
  def initialize(context:, api: nil)
186
- super(context: context, basic_options: api.nil?)
187
- return if context.only_manual?
188
- @api_node =
189
- if !api.nil?
190
- # this can be Api::Node or Rest (Shares)
191
- api
192
- elsif OAuth::Factory.bearer_auth?(options.get_option(:password, mandatory: true))
171
+ super(context: context)
172
+ # Api::Node or Rest::Client (Shares), when provided by the host plugin
173
+ @api_node = api
174
+ end
175
+
176
+ # Node API: provided by the host plugin, or built from CLI options on first use.
177
+ # @return [Api::Node, Rest::Client]
178
+ def api_node
179
+ @api_node ||=
180
+ if OAuth::Factory.bearer_auth?(options.get_option(:password, mandatory: true))
193
181
  # info is provided like node_info of aoc
194
182
  Api::Node.new(
195
183
  base_url: options.get_option(:url, mandatory: true),
@@ -213,9 +201,10 @@ module Aspera
213
201
  def browse_gen3(path)
214
202
  folders_to_process = path
215
203
  folders_to_process = [folders_to_process]
204
+ # not a URL query: merged into the POST body of files/browse
216
205
  query = options.get_option(:query) || {}
217
206
  # special parameter: max number of entries in result
218
- max_items = query.delete(RestList::MAX_ITEMS)
207
+ max_items = query.delete(Rest::List::MAX_ITEMS)
219
208
  # special parameter: recursive browsing
220
209
  recursive = query.delete('recursive')
221
210
  # special parameter: only return one entry for the path, even if folder
@@ -233,7 +222,7 @@ module Aspera
233
222
  total_count = nil
234
223
  loop do
235
224
  # example: send_result={'items'=>[{'file'=>"filename1","permissions"=>[{'name'=>'read'},{'name'=>'write'}]}]}
236
- response = @api_node.create('files/browse', query)
225
+ response = api_node.create('files/browse', query)
237
226
  # 'file','symbolic_link'
238
227
  return Result::SingleObject.new(response['self']) if !Node.gen3_entry_folder?(response['self']) || only_path
239
228
  items = response['items']
@@ -251,13 +240,13 @@ module Aspera
251
240
  break if all_items.count >= total_count
252
241
  offset += items.count
253
242
  query['skip'] = offset
254
- RestParameters.instance.spinner_cb.call(all_items.count)
243
+ Rest::Parameters.instance.spinner_cb.call(all_items.count)
255
244
  end
256
245
  query.delete('skip')
257
246
  end
258
247
  return Result::ObjectList.new(all_items)
259
248
  ensure
260
- RestParameters.instance.spinner_cb.call(action: :success)
249
+ Rest::Parameters.instance.spinner_cb.call(action: :success)
261
250
  end
262
251
 
263
252
  # Create async transfer spec request from direction and folders
@@ -299,20 +288,9 @@ module Aspera
299
288
  if (m = Parser.percent_selector(path))
300
289
  Aspera.assert_values(m[:field], ['id'], type: BadArgument) { 'file id' }
301
290
  val = m[:value]
302
- return Api::NodeFileId.new(@api_node, val.nil? || val.empty? ? top_file_id : val)
303
- end
304
- @api_node.resolve_api_fid(top_file_id, path)
305
- end
306
-
307
- # Legacy helper: resolve a NodeFileId by reading path from the CLI argument stream.
308
- # Used only by setup_access_key_do_permission and action_access_keys_do_permission
309
- # (intermediate nodes whose path cannot yet be injected via execute_leaf).
310
- def apifid_from_next_arg(top_file_id)
311
- file_path = options.instance_identifier(description: 'path or %id:<id> or %id:') do |attribute, value|
312
- Aspera.assert_values(attribute, ['id'], type: BadArgument) { 'file id' }
313
- return Api::NodeFileId.new(@api_node, value.nil? || value.empty? ? top_file_id : value)
291
+ return Api::NodeFileId.new(api_node, val.nil? || val.empty? ? top_file_id : val)
314
292
  end
315
- @api_node.resolve_api_fid(top_file_id, file_path)
293
+ api_node.resolve_api_fid(top_file_id, path)
316
294
  end
317
295
 
318
296
  # Search /async by name
@@ -322,8 +300,8 @@ module Aspera
322
300
  # @raise [Cli::BadArgument] if no such sync, or not by name
323
301
  def async_lookup(field, value)
324
302
  Aspera.assert_values(field, ['name'], type: Cli::BadArgument) { 'search field' }
325
- async_ids = @api_node.read('async/list')['sync_ids']
326
- summaries = @api_node.create('async/summary', {'syncs' => async_ids})['sync_summaries']
303
+ async_ids = api_node.read('async/list')['sync_ids']
304
+ summaries = api_node.create('async/summary', {'syncs' => async_ids})['sync_summaries']
327
305
  selected = summaries.find { |s| s['name'].eql?(value) }
328
306
  raise Cli::BadIdentifier.new('sync', value, field: field) if selected.nil?
329
307
  return selected['snid']
@@ -335,7 +313,7 @@ module Aspera
335
313
  # @return [String] the resolved access key id
336
314
  def lookup_access_key_self_id(field, value, **)
337
315
  Aspera.assert(field.eql?('id') && value.eql?('self'), type: BadArgument) { 'only selector: %id:self' }
338
- @api_node.read('access_keys/self')['id']
316
+ api_node.read('access_keys/self')['id']
339
317
  end
340
318
 
341
319
  # Search /asyncs by name
@@ -345,8 +323,8 @@ module Aspera
345
323
  # @raise [Cli::BadArgument] if no such sync, or not by name
346
324
  def ssync_lookup(field, value)
347
325
  Aspera.assert_values(field, ['name'], type: Cli::BadArgument) { 'search field' }
348
- @api_node.read('asyncs')['ids'].each do |id|
349
- sync_info = @api_node.read("asyncs/#{id}")['configuration']
326
+ api_node.read('asyncs')['ids'].each do |id|
327
+ sync_info = api_node.read("asyncs/#{id}")['configuration']
350
328
  # name is unique, so we can return
351
329
  return id if sync_info[field].eql?(value)
352
330
  end
@@ -359,17 +337,16 @@ module Aspera
359
337
  # :sync is declared separately (intermediate node with sub-commands).
360
338
  GEN3_NODE_ACTIONS = {
361
339
  ls: ->(path:, **) { browse_gen3(path) },
362
- transport: ->(**) { Result::SingleObject.new(@api_node.transport_params) },
363
- spec: ->(**) { Result::SingleObject.new(@api_node.base_spec, fields: Formatter.all_but(Transfer::Spec::SPECIFIC)) },
364
- api_details: ->(**) { Result::SingleObject.new({base_url: @api_node.base_url}.merge(@api_node.params)) },
365
- events: ->(**) { Result::ObjectList.new(@api_node.read('events', query_read_delete(schema: Schema::Registry.query_params(Schema::Registry::NODE, 'events'))), fields: ->(f) { !f.start_with?('data') }) },
366
- info: ->(**) { Result::SingleObject.new(@api_node.read('info')) },
367
- slash: ->(**) { Result::SingleObject.new(@api_node.read('')) },
368
- license: ->(**) { Result::SingleObject.new(@api_node.read('license')) }
340
+ transport: ->(**) { Result::SingleObject.new(api_node.transport_params) },
341
+ spec: ->(**) { Result::SingleObject.new(api_node.base_spec, fields: Formatter.all_but(*Transfer::Spec::SPECIFIC)) },
342
+ api_details: ->(**) { Result::SingleObject.new({base_url: api_node.base_url}.merge(api_node.params)) },
343
+ events: ->(**) { Result::ObjectList.new(api_node.read('events', query_read_delete(schema: Schema::Registry.query_params(Schema::Registry::NODE, 'events'))), fields: ->(f) { !f.start_with?('data') }) },
344
+ info: ->(**) { Result::SingleObject.new(api_node.read('info')) },
345
+ slash: ->(**) { Result::SingleObject.new(api_node.read('')) },
346
+ license: ->(**) { Result::SingleObject.new(api_node.read('license')) }
369
347
  }.freeze
370
348
  private_constant :GEN3_NODE_ACTIONS
371
349
  COMMANDS_GEN3_SPEC.each do |cmd, spec|
372
- next if cmd.eql?(:sync) # intermediate node with sub-commands
373
350
  next if cmd.eql?(:access_keys) # intermediate node declared separately below
374
351
  action = GEN3_NODE_ACTIONS[cmd]
375
352
  command cmd, **spec, **(action ? {action: action} : {})
@@ -389,9 +366,9 @@ module Aspera
389
366
  arguments: [{name: :access_key_id, type: :identifier}],
390
367
  setup: :setup_access_key_do
391
368
  command :set_bearer_key, description: 'Set bearer key on access key',
392
- arguments: [{name: :access_key_id}, {name: :bearer_key_pem, type: String}]
369
+ arguments: [{name: :access_key_id, type: :identifier}, {name: :bearer_key_pem}]
393
370
  crud_commands entity: 'access_keys',
394
- api: :@api_node,
371
+ api: :api_node,
395
372
 
396
373
  body_component: Schema::Registry::NODE,
397
374
  lookup: :lookup_access_key_self_id
@@ -401,7 +378,10 @@ module Aspera
401
378
  COMMANDS_GEN4_SPEC.each do |cmd, spec|
402
379
  command cmd, **spec
403
380
  end
404
- command :permission, description: 'Manage permissions', setup: :setup_access_key_do_permission
381
+ command :v3, description: 'Legacy v3 commands on files', mount: {plugin: self, instance: :v3_node_plugin}
382
+ command :permission, description: 'Manage permissions',
383
+ arguments: [{name: :path, description: 'Path, or %id:<file id>, or %id: for root'}],
384
+ setup: :setup_access_key_do_permission
405
385
  command :sync, description: 'Synchronize folders'
406
386
  commands_under :sync do
407
387
  Sync::Operations::DIRECTIONS.each do |dir|
@@ -414,22 +394,34 @@ module Aspera
414
394
  commands_under %i[access_keys do permission] do
415
395
  command :list, description: 'List permissions on a file'
416
396
  command :show, description: 'Show a permission',
417
- arguments: [{name: :perm_id, type: :identifier}],
418
- action: ->(apifid:, perm_id:, **) { Result::SingleObject.new(apifid.node_api.read("permissions/#{perm_id}")) }
397
+ arguments: [{name: :permission_id, type: :identifier}],
398
+ action: ->(apifid:, permission_id:, **) { Result::SingleObject.new(apifid.node_api.read("permissions/#{permission_id}")) }
419
399
  command :create, description: 'Create a permission',
420
- arguments: [{name: :data, type: Hash, schema: 'node:components.schemas.permissions-post-request'}]
421
- command :modify, description: 'Modify a permission',
422
- arguments: [{name: :perm_id, type: :identifier}, {name: :data, type: Hash, schema: 'node:components.schemas.permissions-id-put-request'}]
400
+ arguments: [{name: :permission, type: Hash, schema: 'node:components.schemas.permissions-post-request'}]
401
+ command(
402
+ :modify, description: 'Modify a permission',
403
+ arguments: [{name: :permission_id, type: :identifier}, {name: :permission, type: Hash, schema: 'node:components.schemas.permissions-id-put-request'}],
404
+ action: lambda do |permission:, apifid:, permission_id:, **|
405
+ apifid.node_api.update("permissions/#{permission_id}", permission)
406
+ Result::Status.new('Updated')
407
+ end
408
+ )
423
409
  command :delete, description: 'Delete permissions',
424
- arguments: [{name: :perm_id, bulk: true}]
410
+ arguments: [{name: :permission_id, type: :identifier, bulk: true}]
425
411
  end
426
412
  # async (legacy /async)
427
- commands_under :async, description: 'synchronization (legacy /async)' do
428
- command :list, description: 'List async sync IDs', action: -> { Result::ValueList.new(@api_node.read('async/list')['sync_ids']) }
413
+ commands_under :async, description: 'Manage legacy synchronizations (/async)' do
414
+ command :list, description: 'List async sync IDs', action: ->(**) { Result::ValueList.new(api_node.read('async/list')['sync_ids']) }
429
415
  command :show, description: 'Show async summary',
430
416
  arguments: [{name: :async_id, type: :identifier, lookup: :async_lookup}]
431
- command :delete, description: 'Delete async',
432
- arguments: [{name: :async_id, type: :identifier, lookup: :async_lookup}]
417
+ command(
418
+ :delete, description: 'Delete async',
419
+ arguments: [{name: :async_id, type: :identifier, lookup: :async_lookup}],
420
+ action: lambda do |async_id:, **|
421
+ async_ids = async_id.eql?(SpecialValues::ALL) ? api_node.read('async/list')['sync_ids'] : [async_id]
422
+ Result::SingleObject.new(api_node.create('async/delete', {'syncs' => async_ids}))
423
+ end
424
+ )
433
425
  command :bandwidth, description: 'Show async bandwidth',
434
426
  arguments: [{name: :async_id, type: :identifier, lookup: :async_lookup}]
435
427
  command :files, description: 'List async files',
@@ -438,93 +430,138 @@ module Aspera
438
430
  arguments: [{name: :async_id, type: :identifier, lookup: :async_lookup}]
439
431
  end
440
432
  # ssync (/asyncs)
441
- # command :ssync, description: 'Manage sync operations (/asyncs)'
442
- commands_under :ssync, description: 'synchronization (/asyncs)' do
433
+ commands_under :ssync, description: 'Manage synchronizations (/asyncs)' do
443
434
  crud_commands entity: 'asyncs',
444
- api: :@api_node,
435
+ name: 'sync session',
436
+ id_name: :ssync_id,
437
+ api: :api_node,
445
438
  operations: %i[create list show delete],
446
439
  items_key: 'ids',
447
440
  lookup: :ssync_lookup
448
- command :start, description: 'Start a sync',
449
- arguments: [{name: :ssync_id, type: :identifier, lookup: :ssync_lookup}],
450
- action: :action_ssync_start
451
- command :stop, description: 'Stop a sync',
452
- arguments: [{name: :ssync_id, type: :identifier, lookup: :ssync_lookup}],
453
- action: :action_ssync_stop
441
+ command(
442
+ :start, description: 'Start a sync', arguments: [{name: :ssync_id, type: :identifier, lookup: :ssync_lookup}],
443
+ action: lambda do |ssync_id:, **|
444
+ api_node.call(operation: 'POST', subpath: "asyncs/#{ssync_id}/start", content_type: Mime::TEXT, body: '', ret: :resp).body
445
+ Result::Status.new('Done')
446
+ end
447
+ )
448
+ command(
449
+ :stop, description: 'Stop a sync', arguments: [{name: :ssync_id, type: :identifier, lookup: :ssync_lookup}],
450
+ action: lambda do |ssync_id:, **|
451
+ api_node.call(operation: 'POST', subpath: "asyncs/#{ssync_id}/stop", content_type: Mime::TEXT, body: '', ret: :resp).body
452
+ Result::Status.new('Done')
453
+ end
454
+ )
454
455
  command :bandwidth, description: 'Show sync bandwidth',
455
456
  arguments: [{name: :ssync_id, type: :identifier, lookup: :ssync_lookup}],
456
- action: ->(ssync_id:, **) { Result::SingleObject.new(@api_node.read("asyncs/#{ssync_id}/bandwidth", options.get_option(:query) || {})) }
457
+ action: ->(ssync_id:, **) { Result::SingleObject.new(api_node.read("asyncs/#{ssync_id}/bandwidth", query_read_delete(default: {}))) }
457
458
  command :counters, description: 'Show sync counters',
458
459
  arguments: [{name: :ssync_id, type: :identifier, lookup: :ssync_lookup}],
459
- action: ->(ssync_id:, **) { Result::SingleObject.new(@api_node.read("asyncs/#{ssync_id}/counters", options.get_option(:query) || {})) }
460
+ action: ->(ssync_id:, **) { Result::SingleObject.new(api_node.read("asyncs/#{ssync_id}/counters", query_read_delete(default: {}))) }
460
461
  command :files, description: 'List sync files',
461
462
  arguments: [{name: :ssync_id, type: :identifier, lookup: :ssync_lookup}],
462
- action: ->(ssync_id:, **) { Result::SingleObject.new(@api_node.read("asyncs/#{ssync_id}/files", options.get_option(:query) || {})) }
463
+ action: ->(ssync_id:, **) { Result::SingleObject.new(api_node.read("asyncs/#{ssync_id}/files", query_read_delete(default: {}))) }
463
464
  command :state, description: 'Show sync state',
464
465
  arguments: [{name: :ssync_id, type: :identifier, lookup: :ssync_lookup}],
465
- action: ->(ssync_id:, **) { Result::SingleObject.new(@api_node.read("asyncs/#{ssync_id}/state")) }
466
+ action: ->(ssync_id:, **) { Result::SingleObject.new(api_node.read("asyncs/#{ssync_id}/state")) }
466
467
  command :summary, description: 'Show sync summary',
467
468
  arguments: [{name: :ssync_id, type: :identifier, lookup: :ssync_lookup}],
468
- action: ->(ssync_id:, **) { Result::SingleObject.new(@api_node.read("asyncs/#{ssync_id}/summary")) }
469
+ action: ->(ssync_id:, **) { Result::SingleObject.new(api_node.read("asyncs/#{ssync_id}/summary")) }
469
470
  end
470
471
  # stream
471
472
  command :stream, description: 'Manage stream operations'
472
473
  commands_under :stream do
473
- command :list, description: 'List streams', action: -> { Result::ObjectList.new(@api_node.read('ops/transfers', query_read_delete), fields: %w[id status]) }
474
+ command :list, description: 'List streams', action: ->(**) { Result::ObjectList.new(api_node.read('ops/transfers', query_read_delete), fields: %w[id status]) }
474
475
  command :create, description: 'Create a stream',
475
- arguments: [{name: :data, type: Hash, schema: 'node:components.schemas.transferPostRequest'}],
476
- action: ->(data:, **) { Result::SingleObject.new(@api_node.create('streams', data)) }
476
+ arguments: [{name: :stream, type: Hash, schema: 'node:components.schemas.transferPostRequest'}],
477
+ action: ->(stream:, **) { Result::SingleObject.new(api_node.create('streams', stream)) }
477
478
  command :show, description: 'Show a stream',
478
479
  arguments: [{name: :transfer_id, type: :identifier}],
479
- action: ->(transfer_id:, **) { Result::SingleObject.new(@api_node.read("ops/transfers/#{transfer_id}")) }
480
+ action: ->(transfer_id:, **) { Result::SingleObject.new(api_node.read("ops/transfers/#{transfer_id}")) }
480
481
  command :modify, description: 'Modify a stream',
481
- arguments: [{name: :transfer_id, type: :identifier}, {name: :data, type: Hash, schema: 'node:components.schemas.transferPutRequest'}],
482
- action: ->(data:, transfer_id:, **) { Result::SingleObject.new(@api_node.update("streams/#{transfer_id}", data)) }
482
+ arguments: [{name: :transfer_id, type: :identifier}, {name: :stream, type: Hash, schema: 'node:components.schemas.transferPutRequest'}],
483
+ action: ->(stream:, transfer_id:, **) { Result::SingleObject.new(api_node.update("streams/#{transfer_id}", stream)) }
483
484
  command :cancel, description: 'Cancel a stream',
484
485
  arguments: [{name: :transfer_id, type: :identifier}],
485
- action: ->(transfer_id:, **) { Result::SingleObject.new(@api_node.cancel("streams/#{transfer_id}")) }
486
+ action: ->(transfer_id:, **) { Result::SingleObject.new(api_node.cancel("streams/#{transfer_id}")) }
486
487
  end
487
488
  # transfer
488
489
  command :transfer, description: 'Manage transfer operations'
489
490
  commands_under :transfer do
490
491
  command :list, description: 'List transfers'
491
- command :cancel, description: 'Cancel a transfer',
492
- arguments: [{name: :transfer_id, type: :identifier}]
492
+ command(
493
+ :cancel, description: 'Cancel a transfer',
494
+ arguments: [{name: :transfer_id, type: :identifier}],
495
+ action: lambda do |transfer_id:, **|
496
+ api_node.cancel("ops/transfers/#{transfer_id}")
497
+ Result::Status.new('Cancelled')
498
+ end
499
+ )
493
500
  command :show, description: 'Show a transfer',
494
501
  arguments: [{name: :transfer_id, type: :identifier}],
495
- action: ->(transfer_id:, **) { Result::SingleObject.new(@api_node.read("ops/transfers/#{transfer_id}")) }
496
- command :modify, description: 'Modify a transfer',
497
- arguments: [{name: :transfer_id, type: :identifier}, {name: :update_value, type: Hash, schema: 'node:components.schemas.transferPutRequest'}]
502
+ action: ->(transfer_id:, **) { Result::SingleObject.new(api_node.read("ops/transfers/#{transfer_id}")) }
503
+ command(
504
+ :modify, description: 'Modify a transfer',
505
+ arguments: [{name: :transfer_id, type: :identifier}, {name: :transfer, type: Hash, schema: 'node:components.schemas.transferPutRequest'}],
506
+ action: lambda do |transfer:, transfer_id:, **|
507
+ api_node.update("ops/transfers/#{transfer_id}", transfer)
508
+ Result::Status.new('Modified')
509
+ end
510
+ )
498
511
  command :bandwidth_average, description: 'Show average bandwidth per period'
499
512
  command :sessions, description: 'List transfer sessions'
500
513
  end
501
514
  # service
502
515
  command :service, description: 'Manage services'
503
516
  commands_under :service do
504
- command :list, description: 'List services', action: -> { Result::ObjectList.new(@api_node.read('rund/services')['services']) }
505
- command :create, description: 'Create a service',
506
- arguments: [{name: :creation_data, type: Hash}]
507
- command :delete, description: 'Delete a service',
508
- arguments: [{name: :service_id, type: :identifier}]
517
+ command :list, description: 'List services', action: ->(**) { Result::ObjectList.new(api_node.read('rund/services')['services']) }
518
+ command(
519
+ :create, description: 'Create a service',
520
+ arguments: [{name: :service, type: Hash}],
521
+ action: lambda do |service:, **|
522
+ resp = api_node.create('rund/services', service)
523
+ Result::Status.new("#{resp['id']} created")
524
+ end
525
+ )
526
+ command(
527
+ :delete, description: 'Delete a service',
528
+ arguments: [{name: :service_id, type: :identifier}],
529
+ action: lambda do |service_id:, **|
530
+ api_node.delete("rund/services/#{service_id}")
531
+ Result::Status.new("#{service_id} deleted")
532
+ end
533
+ )
509
534
  end
510
535
  # watch_folder
511
536
  command :watch_folder, description: 'Manage watch folders', setup: :setup_watch_folder
512
537
  commands_under :watch_folder do
513
538
  command :create, description: 'Create a watch folder',
514
- arguments: [{name: :data, type: Hash}],
515
- action: ->(data:, **) { Result::Status.new("#{@api_node.create('v3/watchfolders', data)['id']} created") }
539
+ arguments: [{name: :watch_folder, type: Hash}],
540
+ action: ->(watch_folder:, **) { Result::Status.new("#{api_node.create('v3/watchfolders', watch_folder)['id']} created") }
516
541
  command :list, description: 'List watch folders',
517
- action: -> { Result::ValueList.new(@api_node.read('v3/watchfolders', query_read_delete)['ids']) }
542
+ action: ->(**) { Result::ValueList.new(api_node.read('v3/watchfolders', query_read_delete)['ids']) }
518
543
  command :show, description: 'Show a watch folder',
519
544
  arguments: [{name: :watch_folder_id, type: :identifier}],
520
- action: ->(watch_folder_id:, **) { Result::SingleObject.new(@api_node.read("v3/watchfolders/#{watch_folder_id}")) }
521
- command :modify, description: 'Modify a watch folder',
522
- arguments: [{name: :watch_folder_id, type: :identifier}, {name: :data, type: Hash}]
523
- command :delete, description: 'Delete a watch folder',
524
- arguments: [{name: :watch_folder_id, type: :identifier}]
525
- command :state, description: 'Show watch folder state',
545
+ action: ->(watch_folder_id:, **) { Result::SingleObject.new(api_node.read("v3/watchfolders/#{watch_folder_id}")) }
546
+ command(
547
+ :modify, description: 'Modify a watch folder',
548
+ arguments: [{name: :watch_folder_id, type: :identifier}, {name: :watch_folder, type: Hash}],
549
+ action: lambda do |watch_folder:, watch_folder_id:, **|
550
+ api_node.update("v3/watchfolders/#{watch_folder_id}", watch_folder)
551
+ Result::Status.new("#{watch_folder_id} updated")
552
+ end
553
+ )
554
+ command(
555
+ :delete, description: 'Delete a watch folder',
556
+ arguments: [{name: :watch_folder_id, type: :identifier}],
557
+ action: lambda do |watch_folder_id:, **|
558
+ api_node.delete("v3/watchfolders/#{watch_folder_id}")
559
+ Result::Status.new("#{watch_folder_id} deleted")
560
+ end
561
+ )
562
+ command :state, description: 'Show watch folder state',
526
563
  arguments: [{name: :watch_folder_id, type: :identifier}],
527
- action: ->(watch_folder_id:, **) { Result::SingleObject.new(@api_node.read("v3/watchfolders/#{watch_folder_id}/state")) }
564
+ action: ->(watch_folder_id:, **) { Result::SingleObject.new(api_node.read("v3/watchfolders/#{watch_folder_id}/state")) }
528
565
  end
529
566
  # central
530
567
  command :central, description: 'Query Central service'
@@ -534,19 +571,26 @@ module Aspera
534
571
  end
535
572
  commands_under %i[central session] do
536
573
  command :list, description: 'List sessions',
537
- arguments: [{name: :request_data, type: Hash, mandatory: false, default: nil}]
574
+ arguments: [{name: :criteria, type: Hash, mandatory: false, default: nil}]
538
575
  end
539
576
  commands_under %i[central file] do
540
577
  command :list, description: 'List file transfers',
541
- arguments: [{name: :request_data, type: Hash, mandatory: false, default: nil}]
578
+ arguments: [{name: :criteria, type: Hash, mandatory: false, default: nil}]
542
579
  command :modify, description: 'Modify file transfer validation',
543
- arguments: [{name: :request_data, type: Hash, mandatory: false, default: nil}]
580
+ arguments: [{name: :file, type: Hash, mandatory: false, default: nil}]
544
581
  end
545
582
  # Standalone leaf commands
546
583
  command :asperabrowser, description: 'Open Aspera browser'
547
- command :basic_token, description: 'Generate basic auth token', action: -> { Result::Text.new(Rest.basic_authorization(options.get_option(:username, mandatory: true), options.get_option(:password, mandatory: true))) }
548
- command :bearer_token, description: 'Generate bearer token',
549
- arguments: [{name: :private_key_pem, type: String}, {name: :user_group_id, type: Hash, schema: 'opts:components.schemas.NodeBearerTokenOptions'}]
584
+ command :basic_token, description: 'Generate basic auth token', action: ->(**) { Result::Text.new(Rest.basic_authorization(options.get_option(:username, mandatory: true), options.get_option(:password, mandatory: true))) }
585
+ command(
586
+ :bearer_token, description: 'Generate bearer token',
587
+ arguments: [{name: :private_key_pem}, {name: :token, type: Hash, schema: 'opts:components.schemas.NodeBearerTokenOptions'}],
588
+ action: lambda do |private_key_pem:, token:, **|
589
+ private_key = OpenSSL::PKey::RSA.new(private_key_pem)
590
+ access_key = options.get_option(:username, mandatory: true)
591
+ Result::Text.new(Api::Node.bearer_token(payload: token, access_key: access_key, private_key: private_key))
592
+ end
593
+ )
550
594
  command :simulator, description: 'Start node simulator',
551
595
  arguments: [{name: :parameters, type: Hash, mandatory: false, default: {}, schema: 'opts:components.schemas.NodeSimulatorOptions'}]
552
596
  command :telemetry, description: 'Report telemetry to external system',
@@ -557,15 +601,16 @@ module Aspera
557
601
  def action_delete(paths:, **)
558
602
  # TODO: add query for recursive
559
603
  paths_to_delete = Array(paths)
560
- resp = @api_node.create('files/delete', {paths: paths_to_delete.map { |i| {'path' => i.start_with?('/') ? i : "/#{i}"} }})
604
+ resp = api_node.create('files/delete', {paths: paths_to_delete.map { |i| {'path' => i.start_with?('/') ? i : "/#{i}"} }})
561
605
  cli_result_from_paths_response(resp, 'file deleted')
562
606
  end
563
607
 
564
- def action_search(search_root:, **)
565
- parameters = {'path' => search_root}
608
+ def action_search(path:, **)
609
+ parameters = {'path' => path}
610
+ # not a URL query: merged into the POST body of files/search
566
611
  other_options = options.get_option(:query)
567
612
  parameters.merge!(other_options) unless other_options.nil?
568
- resp = @api_node.create('files/search', parameters)
613
+ resp = api_node.create('files/search', parameters)
569
614
  return Result::Empty.new if resp['items'].empty?
570
615
  fields = resp['items'].first.keys.reject { |i| SEARCH_REMOVE_FIELDS.include?(i) }
571
616
  formatter.display_item_count(resp['item_count'], resp['total_count'])
@@ -573,32 +618,32 @@ module Aspera
573
618
  Result::ObjectList.new(resp['items'], fields: fields)
574
619
  end
575
620
 
576
- def action_space(path_list:, **)
577
- path_list = Array(path_list)
578
- resp = @api_node.create('space', {'paths' => path_list.map { |i| {path: i} }})
621
+ def action_space(paths:, **)
622
+ paths = Array(paths)
623
+ resp = api_node.create('space', {'paths' => paths.map { |i| {path: i} }})
579
624
  Result::ObjectList.new(resp['paths'])
580
625
  end
581
626
 
582
- def action_mkdir(path_list:, **)
583
- path_list = Array(path_list)
584
- resp = @api_node.create('files/create', {'paths' => path_list.map { |i| {type: :directory, path: i} }})
627
+ def action_mkdir(paths:, **)
628
+ paths = Array(paths)
629
+ resp = api_node.create('files/create', {'paths' => paths.map { |i| {type: :directory, path: i} }})
585
630
  cli_result_from_paths_response(resp, 'folder created')
586
631
  end
587
632
 
588
633
  def action_mklink(target:, link_path:, **)
589
- resp = @api_node.create('files/create', {'paths' => [{type: :symbolic_link, path: link_path, target: {path: target}}]})
634
+ resp = api_node.create('files/create', {'paths' => [{type: :symbolic_link, path: link_path, target: {path: target}}]})
590
635
  cli_result_from_paths_response(resp, 'link created')
591
636
  end
592
637
 
593
- def action_mkfile(file_path:, contents:, **)
638
+ def action_mkfile(path:, contents:, **)
594
639
  contents64 = contents.nil? ? '' : Base64.strict_encode64(contents)
595
- resp = @api_node.create('files/create', {'paths' => [{type: :file, path: file_path, contents: contents64}]})
640
+ resp = api_node.create('files/create', {'paths' => [{type: :file, path: path, contents: contents64}]})
596
641
  cli_result_from_paths_response(resp, 'file created')
597
642
  end
598
643
 
599
- def action_rename(path_base:, path_src:, path_dst:, **)
644
+ def action_rename(folder:, source:, destination:, **)
600
645
  # TODO: multiple ?
601
- resp = @api_node.create('files/rename', {'paths' => [{'path' => path_base, 'source' => path_src, 'destination' => path_dst}]})
646
+ resp = api_node.create('files/rename', {'paths' => [{'path' => folder, 'source' => source, 'destination' => destination}]})
602
647
  cli_result_from_paths_response(resp, 'entry moved')
603
648
  end
604
649
 
@@ -606,8 +651,8 @@ module Aspera
606
651
  def sync_gen3_block
607
652
  lambda do |direction, local_path, remote_path|
608
653
  request_transfer_spec = sync_spec_request(direction, local_path, remote_path)
609
- @api_node.add_tspec_info(request_transfer_spec) if @api_node.respond_to?(:add_tspec_info)
610
- transfer_spec = @api_node.create(
654
+ api_node.add_tspec_info(request_transfer_spec) if api_node.respond_to?(:add_tspec_info)
655
+ transfer_spec = api_node.create(
611
656
  'files/sync_setup',
612
657
  {transfer_requests: [{transfer_request: request_transfer_spec}]}
613
658
  )['transfer_specs'].first['transfer_spec']
@@ -621,42 +666,42 @@ module Aspera
621
666
  define_method(:"action_sync_#{dir}") { |path:, sync_info: {}, **| run_sync_transfer(dir, path: path, sync_info: sync_info, &sync_gen3_block) }
622
667
  end
623
668
 
624
- def action_upload
669
+ def action_upload(**)
625
670
  # empty transfer spec for authorization request
626
671
  request_transfer_spec = {}
627
672
  request_transfer_spec[:paths] = [{destination: transfer.destination_folder(Transfer::Spec::DIRECTION_SEND)}]
628
673
  # add fixed parameters if any (for COS)
629
- @api_node.add_tspec_info(request_transfer_spec) if @api_node.respond_to?(:add_tspec_info)
674
+ api_node.add_tspec_info(request_transfer_spec) if api_node.respond_to?(:add_tspec_info)
630
675
  Api::Node.add_public_key(request_transfer_spec)
631
676
  setup_payload = {transfer_requests: [{transfer_request: request_transfer_spec}]}
632
- transfer_spec = @api_node.create('files/upload_setup', setup_payload)['transfer_specs'].first['transfer_spec']
677
+ transfer_spec = api_node.create('files/upload_setup', setup_payload)['transfer_specs'].first['transfer_spec']
633
678
  Api::Node.add_private_key(transfer_spec)
634
679
  transfer_spec.delete('paths')
635
680
  Runner.result_transfer(transfer.start(transfer_spec))
636
681
  end
637
682
 
638
- def action_download
683
+ def action_download(**)
639
684
  # empty transfer spec for authorization request
640
685
  request_transfer_spec = {}
641
686
  request_transfer_spec[:paths] = transfer.ts_source_paths
642
687
  # add fixed parameters if any (for COS)
643
- @api_node.add_tspec_info(request_transfer_spec) if @api_node.respond_to?(:add_tspec_info)
688
+ api_node.add_tspec_info(request_transfer_spec) if api_node.respond_to?(:add_tspec_info)
644
689
  Api::Node.add_public_key(request_transfer_spec)
645
690
  setup_payload = {transfer_requests: [{transfer_request: request_transfer_spec}]}
646
- transfer_spec = @api_node.create('files/download_setup', setup_payload)['transfer_specs'].first['transfer_spec']
691
+ transfer_spec = api_node.create('files/download_setup', setup_payload)['transfer_specs'].first['transfer_spec']
647
692
  Api::Node.add_private_key(transfer_spec)
648
693
  Runner.result_transfer(transfer.start(transfer_spec))
649
694
  end
650
695
 
651
- def action_cat(remote_path:, **)
652
- http = @api_node.read("files/#{URI.encode_www_form_component(remote_path)}/contents", ret: :resp)
696
+ def action_cat(path:, **)
697
+ http = api_node.read("files/#{URI.encode_www_form_component(path)}/contents", ret: :resp)
653
698
  Result::Text.new(http.body)
654
699
  end
655
700
 
656
- def action_health
701
+ def action_health(**)
657
702
  nagios = Nagios.new
658
703
  begin
659
- info = @api_node.read('info')
704
+ info = api_node.read('info')
660
705
  nagios.add_ok('node api', 'accessible')
661
706
  nagios.check_time_offset(info['current_time'], 'node api')
662
707
  nagios.check_product_version('node api', 'entsrv', info['version'])
@@ -664,7 +709,7 @@ module Aspera
664
709
  nagios.add_critical('node api', e.to_s)
665
710
  end
666
711
  begin
667
- @api_node.call(
712
+ api_node.call(
668
713
  operation: 'POST',
669
714
  subpath: 'services/soap/Transfer-201210',
670
715
  content_type: Mime::TEXT,
@@ -681,49 +726,32 @@ module Aspera
681
726
 
682
727
  # watch_folder setup: inject required API header (avoids "Unable to convert 2016_09_14 configuration")
683
728
  def setup_watch_folder(**)
684
- @api_node.params[:headers] ||= {}
685
- @api_node.params[:headers]['X-aspera-WF-version'] = '2017_10_23'
729
+ api_node.headers['X-aspera-WF-version'] = '2017_10_23'
686
730
  {}
687
731
  end
688
732
 
689
- def action_access_keys_do_permission_modify(data:, apifid:, perm_id:, **)
690
- apifid.node_api.update("permissions/#{perm_id}", data)
691
- Result::Status.new('Updated')
692
- end
693
-
694
- def action_watch_folder_modify(data:, watch_folder_id:, **)
695
- @api_node.update("v3/watchfolders/#{watch_folder_id}", data)
696
- Result::Status.new("#{watch_folder_id} updated")
697
- end
698
-
699
- def action_watch_folder_delete(watch_folder_id:, **)
700
- @api_node.delete("v3/watchfolders/#{watch_folder_id}")
701
- Result::Status.new("#{watch_folder_id} deleted")
702
- end
703
-
704
733
  # access_keys > do - setup: resolve access key and root file id
705
- # access_key_id is resolved by execute_leaf (via instance_arg in CommandSpec)
706
734
  # @return [Hash] context hash containing :do_root_file_id
707
735
  def setup_access_key_do(access_key_id:, **)
708
736
  @do_root_file_id = options.get_option(:root_id)
709
737
  if @do_root_file_id.nil?
710
- ak_info = @api_node.read("access_keys/#{access_key_id}")
711
- ak_secret = context.secret_finder.lookup(url: @api_node.base_url, username: ak_info['id'])
738
+ ak_info = api_node.read("access_keys/#{access_key_id}")
739
+ ak_secret = context.secret_finder.lookup(url: api_node.base_url, username: ak_info['id'])
712
740
  if !access_key_id.eql?('self')
713
- Aspera.assert(ak_secret, type: Cli::MissingArgument) { "Please provide secret for #{ak_info['id']} using option: secret or by setting a preset for #{ak_info['id']}@#{@api_node.base_url}." }
714
- @api_node.auth_params[:username] = ak_info['id']
715
- @api_node.auth_params[:password] = ak_secret
741
+ Aspera.assert(ak_secret, type: Cli::MissingArgument) { "Please provide secret for #{ak_info['id']} using option: secret or by setting a preset for #{ak_info['id']}@#{api_node.base_url}." }
742
+ api_node.auth_params[:username] = ak_info['id']
743
+ api_node.auth_params[:password] = ak_secret
716
744
  end
717
745
  @do_root_file_id = ak_info['root_file_id']
718
746
  end
719
747
  {do_root_file_id: @do_root_file_id}
720
748
  end
721
749
 
722
- # access_keys > do > permission - setup: resolve apifid from CLI path argument
723
- # (path is read from CLI here as 'permission' is an intermediate node)
750
+ # access_keys > do > permission - setup: resolve apifid from the path argument of the node
751
+ # do_root_file_id: comes from ctx (setup_access_key_do, or seed of a mount)
724
752
  # @return [Hash] context hash containing :apifid
725
- def setup_access_key_do_permission(**)
726
- {apifid: apifid_from_next_arg(@do_root_file_id)}
753
+ def setup_access_key_do_permission(do_root_file_id:, path:, **)
754
+ {apifid: apifid_from_path(do_root_file_id, path)}
727
755
  end
728
756
 
729
757
  # access_keys > do > ls
@@ -737,7 +765,7 @@ module Aspera
737
765
  # access_keys > do > find
738
766
  def action_access_keys_do_find(path:, filter: nil, do_root_file_id:, **)
739
767
  apifid = apifid_from_path(do_root_file_id, path)
740
- Result::ObjectList.new(@api_node.find_files(apifid.file_id, Base.file_matcher(filter)), fields: ['path'])
768
+ Result::ObjectList.new(api_node.find_files(apifid.file_id, Base.file_matcher(filter)), fields: ['path'])
741
769
  end
742
770
 
743
771
  # access_keys > do > cat
@@ -753,9 +781,9 @@ module Aspera
753
781
  end
754
782
 
755
783
  # access_keys > do > modify
756
- def action_access_keys_do_modify(path:, update_value:, do_root_file_id:, **)
784
+ def action_access_keys_do_modify(path:, file:, do_root_file_id:, **)
757
785
  apifid = apifid_from_path(do_root_file_id, path)
758
- apifid.node_api.update("files/#{apifid.file_id}", update_value)
786
+ apifid.node_api.update("files/#{apifid.file_id}", file)
759
787
  Result::Status.new('Done')
760
788
  end
761
789
 
@@ -767,19 +795,19 @@ module Aspera
767
795
 
768
796
  # access_keys > do > rename
769
797
  def action_access_keys_do_rename(source_path:, new_name:, do_root_file_id:, **)
770
- apifid = @api_node.resolve_api_fid(do_root_file_id, source_path)
798
+ apifid = api_node.resolve_api_fid(do_root_file_id, source_path)
771
799
  apifid.node_api.update("files/#{apifid.file_id}", {name: new_name})
772
800
  Result::Status.new("renamed to #{new_name}")
773
801
  end
774
802
 
775
803
  # access_keys > do > delete
776
- def action_access_keys_do_delete(paths:, do_root_file_id:, **)
777
- bulk_result(paths, command: :delete, id_result: 'path') do |l_path|
804
+ def action_access_keys_do_delete(path:, do_root_file_id:, **)
805
+ bulk_result(path, command: :delete, id_result: 'path') do |l_path|
778
806
  apifid = if (m = Parser.percent_selector(l_path))
779
807
  Aspera.assert_values(m[:field], ['id'], type: BadIdentifier)
780
- Api::NodeFileId.new(@api_node, m[:value])
808
+ Api::NodeFileId.new(api_node, m[:value])
781
809
  else
782
- @api_node.resolve_api_fid(do_root_file_id, l_path)
810
+ api_node.resolve_api_fid(do_root_file_id, l_path)
783
811
  end
784
812
  apifid.node_api.delete("files/#{apifid.file_id}")
785
813
  {'path' => l_path}
@@ -790,7 +818,7 @@ module Aspera
790
818
  def sync_gen4_block(do_root_file_id)
791
819
  lambda do |direction, _local_path, remote_path|
792
820
  ts_direction = direction.eql?(:pull) ? Transfer::Spec::DIRECTION_RECEIVE : Transfer::Spec::DIRECTION_SEND
793
- apifid = @api_node.resolve_api_fid(do_root_file_id, remote_path)
821
+ apifid = api_node.resolve_api_fid(do_root_file_id, remote_path)
794
822
  apifid.node_api.transfer_spec_gen4(apifid.file_id, ts_direction)
795
823
  end
796
824
  end
@@ -803,27 +831,20 @@ module Aspera
803
831
 
804
832
  # access_keys > do > upload
805
833
  def action_access_keys_do_upload(do_root_file_id:, **)
806
- apifid = @api_node.resolve_api_fid(do_root_file_id, transfer.destination_folder(Transfer::Spec::DIRECTION_SEND), true)
834
+ apifid = api_node.resolve_api_fid(do_root_file_id, transfer.destination_folder(Transfer::Spec::DIRECTION_SEND), true)
807
835
  Runner.result_transfer(transfer.start(apifid.node_api.transfer_spec_gen4(apifid.file_id, Transfer::Spec::DIRECTION_SEND)))
808
836
  end
809
837
 
810
838
  # access_keys > do > download
811
839
  def action_access_keys_do_download(do_root_file_id:, **)
812
- apifid, source_paths = @api_node.resolve_api_fid_paths(do_root_file_id, transfer.ts_source_paths)
840
+ apifid, source_paths = api_node.resolve_api_fid_paths(do_root_file_id, transfer.ts_source_paths)
813
841
  Runner.result_transfer(transfer.start(apifid.node_api.transfer_spec_gen4(apifid.file_id, Transfer::Spec::DIRECTION_RECEIVE, {'paths'=>source_paths})))
814
842
  end
815
843
 
816
- # access_keys > do > permission (intermediate node: re-enter DSL at permission sub-tree)
817
- def action_access_keys_do_permission(do_root_file_id:, **)
818
- apifid = apifid_from_next_arg(do_root_file_id)
819
- dispatch_from_registry(%i[access_keys do permission], {apifid: apifid}, skip_setup: true)
820
- end
821
-
822
- # access_keys > do > v3
823
- def action_access_keys_do_v3(do_root_file_id:, **)
824
- # Build a fresh Node for the v3 root, then let the DSL dispatcher consume the next command.
825
- v3_node = Node.new(context: context, api: @api_node.resolve_api_fid(do_root_file_id, '').node_api)
826
- v3_node.execute_action
844
+ # access_keys > do > v3 - mount target: Node plugin on the node hosting the root file
845
+ # @return [Node]
846
+ def v3_node_plugin(do_root_file_id:, **)
847
+ Node.new(context: context, api: api_node.resolve_api_fid(do_root_file_id, '').node_api)
827
848
  end
828
849
 
829
850
  # access_keys > do > node_info / bearer_token_node — shared helper builds the result hash
@@ -861,7 +882,8 @@ module Aspera
861
882
  # @param path [String] full path (folder/name)
862
883
  def gen4_mk_resolve(top_file_id, path)
863
884
  containing_folder_path, new_item = Api::Node.split_folder(path)
864
- apifid = @api_node.resolve_api_fid(top_file_id, containing_folder_path, true)
885
+ apifid = api_node.resolve_api_fid(top_file_id, containing_folder_path, true)
886
+ # not a URL query: special keys `check` and `target`, rest merged into the POST body
865
887
  query = options.get_option(:query)
866
888
  check_exists = true
867
889
  payload = {name: new_item}
@@ -869,7 +891,7 @@ module Aspera
869
891
  check_exists = !query.delete('check').eql?(false)
870
892
  target = query.delete('target')
871
893
  if target
872
- target_apifid = @api_node.resolve_api_fid(top_file_id, target, true)
894
+ target_apifid = api_node.resolve_api_fid(top_file_id, target, true)
873
895
  payload[:target_id] = target_apifid.file_id
874
896
  end
875
897
  payload.merge!(query.symbolize_keys)
@@ -913,8 +935,8 @@ module Aspera
913
935
  Result::ObjectList.new(apifid.node_api.read_with_pages('permissions', list_query))
914
936
  end
915
937
 
916
- def action_access_keys_do_permission_delete(perm_id:, apifid:, **)
917
- bulk_result(perm_id, command: :delete) do |one_id|
938
+ def action_access_keys_do_permission_delete(permission_id:, apifid:, **)
939
+ bulk_result(permission_id, command: :delete) do |one_id|
918
940
  apifid.node_api.delete("permissions/#{one_id}")
919
941
  the_app = apifid.node_api.app_info
920
942
  the_app&.api&.permissions_send_event(event_data: {}, app_info: the_app, types: ['permission.deleted'])
@@ -922,8 +944,8 @@ module Aspera
922
944
  end
923
945
  end
924
946
 
925
- def action_access_keys_do_permission_create(data:, apifid:, **)
926
- create_param = data
947
+ def action_access_keys_do_permission_create(permission:, apifid:, **)
948
+ create_param = permission
927
949
  Aspera.assert(!create_param.key?('file_id'), type: Cli::BadArgument) { 'no file_id' }
928
950
  create_param['file_id'] = apifid.file_id
929
951
  create_param['access_levels'] = Api::Node::ACCESS_LEVELS unless create_param.key?('access_levels')
@@ -936,36 +958,31 @@ module Aspera
936
958
 
937
959
  # access_keys > set_bearer_key
938
960
  def action_access_keys_set_bearer_key(access_key_id:, bearer_key_pem:, **)
939
- access_key_id = @api_node.read('access_keys/self')['id'] if access_key_id.eql?('self')
961
+ access_key_id = api_node.read('access_keys/self')['id'] if access_key_id.eql?('self')
940
962
  key = OpenSSL::PKey.read(bearer_key_pem)
941
963
  key = key.public_key if key.private?
942
- @api_node.update("access_keys/#{access_key_id}", {token_verification_key: key.to_pem})
964
+ api_node.update("access_keys/#{access_key_id}", {token_verification_key: key.to_pem})
943
965
  Result::Status.new('public key updated')
944
966
  end
945
967
 
946
968
  # async sub-commands: individual handlers
947
969
  def action_async_show(async_id:, **)
948
- async_ids = @api_node.read('async/list')['sync_ids']
970
+ async_ids = api_node.read('async/list')['sync_ids']
949
971
  if async_id.eql?(SpecialValues::ALL)
950
- resp = @api_node.create('async/summary', {'syncs' => async_ids})['sync_summaries']
972
+ resp = api_node.create('async/summary', {'syncs' => async_ids})['sync_summaries']
951
973
  return Result::Empty.new if resp.empty?
952
974
  return Result::ObjectList.new(resp, fields: %w[snid name local_dir remote_dir])
953
975
  end
954
976
  Integer(async_id)
955
- resp = @api_node.create('async/summary', {'syncs' => [async_id]})['sync_summaries']
977
+ resp = api_node.create('async/summary', {'syncs' => [async_id]})['sync_summaries']
956
978
  return Result::Empty.new if resp.empty?
957
979
  Result::SingleObject.new(resp.first)
958
980
  end
959
981
 
960
- def action_async_delete(async_id:, **)
961
- async_ids = async_id.eql?(SpecialValues::ALL) ? @api_node.read('async/list')['sync_ids'] : [async_id]
962
- Result::SingleObject.new(@api_node.create('async/delete', {'syncs' => async_ids}))
963
- end
964
-
965
982
  def action_async_bandwidth(async_id:, **)
966
983
  Integer(async_id)
967
984
  post_data = {'syncs' => [async_id], 'seconds' => 100}
968
- resp = @api_node.create('async/bandwidth', post_data)
985
+ resp = api_node.create('async/bandwidth', post_data)
969
986
  data = resp['bandwidth_data']
970
987
  return Result::Empty.new if data.empty?
971
988
  Result::ObjectList.new(data.first[async_id]['data'])
@@ -974,9 +991,10 @@ module Aspera
974
991
  def action_async_files(async_id:, **)
975
992
  Integer(async_id)
976
993
  post_data = {'syncs' => [async_id]}
994
+ # not a URL query: merged into the POST body of async/files
977
995
  filter = options.get_option(:query)
978
996
  post_data.merge!(filter) unless filter.nil?
979
- resp = @api_node.create('async/files', post_data)
997
+ resp = api_node.create('async/files', post_data)
980
998
  data = resp['sync_files']
981
999
  data = data.first[async_id] unless data.empty?
982
1000
  iteration_data = []
@@ -997,23 +1015,13 @@ module Aspera
997
1015
 
998
1016
  def action_async_counters(async_id:, **)
999
1017
  Integer(async_id)
1000
- resp = @api_node.create('async/counters', {'syncs' => [async_id]})['sync_counters'].first[async_id].last
1018
+ resp = api_node.create('async/counters', {'syncs' => [async_id]})['sync_counters'].first[async_id].last
1001
1019
  return Result::Empty.new if resp.nil?
1002
1020
  Result::SingleObject.new(resp)
1003
1021
  end
1004
1022
 
1005
- def action_ssync_start(ssync_id:, **)
1006
- @api_node.call(operation: 'POST', subpath: "asyncs/#{ssync_id}/start", content_type: Mime::TEXT, body: '', ret: :resp).body
1007
- Result::Status.new('Done')
1008
- end
1009
-
1010
- def action_ssync_stop(ssync_id:, **)
1011
- @api_node.call(operation: 'POST', subpath: "asyncs/#{ssync_id}/stop", content_type: Mime::TEXT, body: '', ret: :resp).body
1012
- Result::Status.new('Done')
1013
- end
1014
-
1015
1023
  # transfer sub-commands
1016
- def action_transfer_list
1024
+ def action_transfer_list(**)
1017
1025
  transfer_filter = query_read_delete(default: {}, schema: Schema::Registry.query_params(Schema::Registry::NODE, 'ops/transfers'))
1018
1026
  iteration_persistency = nil
1019
1027
  if options.get_option(:once_only, mandatory: true)
@@ -1030,13 +1038,13 @@ module Aspera
1030
1038
  else
1031
1039
  Aspera.assert(!transfer_filter.key?('reset'), 'reset only with once_only', type: Cli::BadArgument)
1032
1040
  end
1033
- transfers_data = @api_node.read_with_paging('ops/transfers', transfer_filter, iteration: iteration_persistency&.data)
1041
+ transfers_data = api_node.read_with_paging('ops/transfers', transfer_filter, iteration: iteration_persistency&.data)
1034
1042
  iteration_persistency&.save
1035
1043
  Result::ObjectList.new(transfers_data, fields: %w[id status start_spec.direction start_spec.remote_user start_spec.remote_host start_spec.destination_path])
1036
1044
  end
1037
1045
 
1038
- def action_transfer_sessions
1039
- transfers_data = @api_node.read('ops/transfers', query_read_delete(schema: Schema::Registry.query_params(Schema::Registry::NODE, 'ops/transfers')))
1046
+ def action_transfer_sessions(**)
1047
+ transfers_data = api_node.read('ops/transfers', query_read_delete(schema: Schema::Registry.query_params(Schema::Registry::NODE, 'ops/transfers')))
1040
1048
  sessions = transfers_data.flat_map { |t| t['sessions'] }
1041
1049
  sessions.each do |session|
1042
1050
  SESSION_TIME_FIELDS.each do |what|
@@ -1046,18 +1054,8 @@ module Aspera
1046
1054
  Result::ObjectList.new(sessions, fields: %w[id status start_time end_time target_rate_kbps])
1047
1055
  end
1048
1056
 
1049
- def action_transfer_cancel(transfer_id:, **)
1050
- @api_node.cancel("ops/transfers/#{transfer_id}")
1051
- Result::Status.new('Cancelled')
1052
- end
1053
-
1054
- def action_transfer_modify(update_value:, transfer_id:, **)
1055
- @api_node.update("ops/transfers/#{transfer_id}", update_value)
1056
- Result::Status.new('Modified')
1057
- end
1058
-
1059
- def action_transfer_bandwidth_average
1060
- transfers_data = @api_node.read('ops/transfers', query_read_delete(schema: Schema::Registry.query_params(Schema::Registry::NODE, 'ops/transfers')))
1057
+ def action_transfer_bandwidth_average(**)
1058
+ transfers_data = api_node.read('ops/transfers', query_read_delete(schema: Schema::Registry.query_params(Schema::Registry::NODE, 'ops/transfers')))
1061
1059
  bandwidth_period = {}
1062
1060
  dir_info = %i[avg_kbps sessions].freeze
1063
1061
  transfers_data.each do |t|
@@ -1086,17 +1084,6 @@ module Aspera
1086
1084
  Result::ObjectList.new(result)
1087
1085
  end
1088
1086
 
1089
- # service sub-commands
1090
- def action_service_create(creation_data:, **)
1091
- resp = @api_node.create('rund/services', creation_data)
1092
- Result::Status.new("#{resp['id']} created")
1093
- end
1094
-
1095
- def action_service_delete(service_id:, **)
1096
- @api_node.delete("rund/services/#{service_id}")
1097
- Result::Status.new("#{service_id} deleted")
1098
- end
1099
-
1100
1087
  # central: shared helper
1101
1088
  def central_validation
1102
1089
  validator_id = options.get_option(:validator)
@@ -1104,35 +1091,35 @@ module Aspera
1104
1091
  end
1105
1092
 
1106
1093
  # central > session > list
1107
- def action_central_session_list(request_data:, **)
1108
- request_data ||= {}
1094
+ def action_central_session_list(criteria:, **)
1095
+ criteria ||= {}
1109
1096
  validation = central_validation
1110
- request_data.deep_merge!({'validation' => validation}) unless validation.nil?
1111
- resp = @api_node.create('services/rest/transfers/v1/sessions', request_data)
1097
+ criteria.deep_merge!({'validation' => validation}) unless validation.nil?
1098
+ resp = api_node.create('services/rest/transfers/v1/sessions', criteria)
1112
1099
  Result::ObjectList.new(resp['session_info_result']['session_info'], fields: %w[session_uuid status transport direction bytes_transferred])
1113
1100
  end
1114
1101
 
1115
1102
  # central > file > list
1116
- def action_central_file_list(request_data:, **)
1117
- request_data ||= {}
1103
+ def action_central_file_list(criteria:, **)
1104
+ criteria ||= {}
1118
1105
  validation = central_validation
1119
- request_data.deep_merge!({'validation' => validation}) unless validation.nil?
1120
- resp = @api_node.create('services/rest/transfers/v1/files', request_data)
1106
+ criteria.deep_merge!({'validation' => validation}) unless validation.nil?
1107
+ resp = api_node.create('services/rest/transfers/v1/files', criteria)
1121
1108
  resp = JSON.parse(resp) if resp.is_a?(String)
1122
1109
  Log.dump(:resp, resp)
1123
1110
  Result::ObjectList.new(resp['file_transfer_info_result']['file_transfer_info'], fields: %w[session_uuid file_id status path])
1124
1111
  end
1125
1112
 
1126
1113
  # central > file > modify
1127
- def action_central_file_modify(request_data:, **)
1128
- request_data ||= {}
1114
+ def action_central_file_modify(file:, **)
1115
+ file ||= {}
1129
1116
  validation = central_validation
1130
- request_data.deep_merge!(validation) unless validation.nil?
1131
- @api_node.update('services/rest/transfers/v1/files', request_data)
1117
+ file.deep_merge!(validation) unless validation.nil?
1118
+ api_node.update('services/rest/transfers/v1/files', file)
1132
1119
  Result::Status.new('updated')
1133
1120
  end
1134
1121
 
1135
- def action_asperabrowser
1122
+ def action_asperabrowser(**)
1136
1123
  browse_params = {
1137
1124
  'nodeUser' => options.get_option(:username, mandatory: true),
1138
1125
  'nodePW' => options.get_option(:password, mandatory: true),
@@ -1144,12 +1131,6 @@ module Aspera
1144
1131
  return Result::Status.new('done')
1145
1132
  end
1146
1133
 
1147
- def action_bearer_token(private_key_pem:, user_group_id:, **)
1148
- private_key = OpenSSL::PKey::RSA.new(private_key_pem)
1149
- access_key = options.get_option(:username, mandatory: true)
1150
- Result::Text.new(Api::Node.bearer_token(payload: user_group_id, access_key: access_key, private_key: private_key))
1151
- end
1152
-
1153
1134
  def action_simulator(parameters: {}, **)
1154
1135
  require 'aspera/node_simulator'
1155
1136
  parameters = parameters.symbolize_keys
@@ -1169,8 +1150,8 @@ module Aspera
1169
1150
  parameters[:interval] = 10 unless parameters.key?(:interval)
1170
1151
  parameters[:hostname] = Socket.gethostname unless parameters.key?(:hostname)
1171
1152
  interval = parameters[:interval].to_f
1172
- Aspera.assert(interval > 0, type: Cli::BadArgument) { 'Interval must be a positive number in seconds' }
1173
- otel_api = Rest.new(
1153
+ Aspera.assert(interval >= 0, type: Cli::BadArgument) { 'Interval must be a positive number in seconds, or 0 for single shot' }
1154
+ otel_api = Rest::Client.new(
1174
1155
  base_url: "#{parameters[:url]}/v1",
1175
1156
  headers: {
1176
1157
  # 'Authorization' => "apiToken #{parameters[:key]}",
@@ -1225,7 +1206,7 @@ module Aspera
1225
1206
  }
1226
1207
  loop do
1227
1208
  timestamp = Time.now
1228
- transfers_data = @api_node.read_with_paging('ops/transfers', {active_only: true})
1209
+ transfers_data = api_node.read_with_paging('ops/transfers', {active_only: true})
1229
1210
  datapoint[:asInt] = transfers_data.length
1230
1211
  datapoint[:timeUnixNano] = timestamp.to_i * 1_000_000_000 + timestamp.nsec
1231
1212
  Log.log.info("#{datapoint[:asInt]} active transfers")
@@ -1236,21 +1217,6 @@ module Aspera
1236
1217
  end
1237
1218
  end
1238
1219
 
1239
- # Dispatch a Gen3 command that has already been read from the argument stream.
1240
- # Used when a new Node instance is created by an external plugin (shares, faspex, cos, :v3)
1241
- # and the command symbol was already consumed before the Node was instantiated.
1242
- # Re-enters the DSL registry so Proc actions and intermediate nodes (sync, access_keys)
1243
- # are resolved normally.
1244
- # Pre-resolved CLI arguments (e.g. path:) can be forwarded via +ctx+ to avoid
1245
- # re-consuming tokens that were already resolved by the calling plugin's DSL.
1246
- # @param command [Symbol] command already consumed from the argument stream
1247
- # @param ctx [Hash] pre-resolved context (keyword args forwarded to dispatch)
1248
- # @return [Object] CLI result
1249
- def dispatch_v3_command(command, **ctx)
1250
- Aspera.assert_values(command, COMMANDS_GEN3 + %i[health events info slash license api_details access_keys transfer]) { 'v3 command' }
1251
- dispatch_from_registry([command], ctx)
1252
- end
1253
-
1254
1220
  private
1255
1221
 
1256
1222
  # Response has key `paths`.