aspera-cli 4.27.0 → 4.27.2

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 (142) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +46 -1
  4. data/CONTRIBUTING.md +1 -7
  5. data/TODO.md +50 -0
  6. data/docs/README.md +709 -489
  7. data/lib/aspera/agent/base.rb +10 -2
  8. data/lib/aspera/agent/connect.rb +7 -7
  9. data/lib/aspera/agent/desktop.rb +3 -3
  10. data/lib/aspera/agent/direct.rb +48 -19
  11. data/lib/aspera/agent/factory.rb +3 -3
  12. data/lib/aspera/agent/httpgw.rb +55 -14
  13. data/lib/aspera/agent/node.rb +2 -2
  14. data/lib/aspera/agent/transferd.rb +12 -12
  15. data/lib/aspera/api/aoc.rb +28 -28
  16. data/lib/aspera/api/cos_node.rb +10 -9
  17. data/lib/aspera/api/faspex.rb +2 -2
  18. data/lib/aspera/api/httpgw.rb +20 -20
  19. data/lib/aspera/api/node.rb +31 -53
  20. data/lib/aspera/ascmd.rb +13 -13
  21. data/lib/aspera/ascp/installation.rb +43 -36
  22. data/lib/aspera/ascp/management.rb +6 -6
  23. data/lib/aspera/assert.rb +12 -13
  24. data/lib/aspera/cli/ascp_actions.rb +7 -7
  25. data/lib/aspera/cli/async_transfer_store.rb +49 -5
  26. data/lib/aspera/cli/bootstrapper.rb +9 -9
  27. data/lib/aspera/cli/command_registry.rb +1 -1
  28. data/lib/aspera/cli/command_spec.rb +6 -3
  29. data/lib/aspera/cli/context.rb +3 -3
  30. data/lib/aspera/cli/extended_value.rb +26 -26
  31. data/lib/aspera/cli/formatter.rb +76 -54
  32. data/lib/aspera/cli/gem_checker.rb +1 -1
  33. data/lib/aspera/cli/hints.rb +2 -2
  34. data/lib/aspera/cli/http.rb +22 -29
  35. data/lib/aspera/cli/mailer.rb +7 -7
  36. data/lib/aspera/cli/mcp_tool.rb +31 -4
  37. data/lib/aspera/cli/option_declarator.rb +77 -0
  38. data/lib/aspera/cli/options.schema.yaml +273 -3
  39. data/lib/aspera/cli/parser.rb +539 -280
  40. data/lib/aspera/cli/plugins/alee.rb +1 -1
  41. data/lib/aspera/cli/plugins/aoc.rb +303 -314
  42. data/lib/aspera/cli/plugins/ats.rb +25 -21
  43. data/lib/aspera/cli/plugins/base.rb +357 -186
  44. data/lib/aspera/cli/plugins/basic_auth.rb +0 -12
  45. data/lib/aspera/cli/plugins/config.rb +76 -73
  46. data/lib/aspera/cli/plugins/console.rb +8 -8
  47. data/lib/aspera/cli/plugins/cos.rb +4 -3
  48. data/lib/aspera/cli/plugins/factory.rb +2 -2
  49. data/lib/aspera/cli/plugins/faspex5.rb +259 -248
  50. data/lib/aspera/cli/plugins/faspio.rb +7 -2
  51. data/lib/aspera/cli/plugins/httpgw.rb +1 -1
  52. data/lib/aspera/cli/plugins/mcp.rb +11 -11
  53. data/lib/aspera/cli/plugins/node.rb +227 -283
  54. data/lib/aspera/cli/plugins/orchestrator.rb +23 -23
  55. data/lib/aspera/cli/plugins/preview.rb +61 -64
  56. data/lib/aspera/cli/plugins/server.rb +36 -31
  57. data/lib/aspera/cli/plugins/shares.rb +174 -135
  58. data/lib/aspera/cli/preset_actions.rb +39 -19
  59. data/lib/aspera/cli/preset_manager.rb +74 -32
  60. data/lib/aspera/cli/result.rb +18 -16
  61. data/lib/aspera/cli/runner.rb +75 -39
  62. data/lib/aspera/cli/secret_finder.rb +1 -1
  63. data/lib/aspera/cli/sync_actions.rb +36 -35
  64. data/lib/aspera/cli/terminal_formatter.rb +2 -2
  65. data/lib/aspera/cli/transfer_actions.rb +17 -10
  66. data/lib/aspera/cli/transfer_agent.rb +44 -44
  67. data/lib/aspera/cli/transfer_progress.rb +7 -7
  68. data/lib/aspera/cli/vault_manager.rb +34 -12
  69. data/lib/aspera/cli/version.rb +1 -1
  70. data/lib/aspera/cli/wizard.rb +17 -20
  71. data/lib/aspera/colors.rb +3 -3
  72. data/lib/aspera/command_line_builder.rb +17 -16
  73. data/lib/aspera/command_line_converter.rb +1 -1
  74. data/lib/aspera/coverage.rb +1 -2
  75. data/lib/aspera/data_repository.rb +1 -1
  76. data/lib/aspera/dot_container.rb +8 -8
  77. data/lib/aspera/environment.rb +13 -13
  78. data/lib/aspera/faspex_gw.rb +4 -4
  79. data/lib/aspera/faspex_postproc.rb +14 -9
  80. data/lib/aspera/graphql.rb +4 -2
  81. data/lib/aspera/hash_ext.rb +2 -2
  82. data/lib/aspera/json_rpc/client.rb +1 -1
  83. data/lib/aspera/keychain/base.rb +10 -3
  84. data/lib/aspera/keychain/encrypted_hash.rb +92 -17
  85. data/lib/aspera/keychain/factory.rb +17 -5
  86. data/lib/aspera/keychain/hashicorp_vault.rb +1 -1
  87. data/lib/aspera/keychain/macos_security.rb +23 -25
  88. data/lib/aspera/keychain/one_password_api.rb +86 -0
  89. data/lib/aspera/keychain/one_password_base.rb +34 -0
  90. data/lib/aspera/keychain/one_password_cli.rb +98 -0
  91. data/lib/aspera/link_header.rb +2 -2
  92. data/lib/aspera/log.rb +13 -13
  93. data/lib/aspera/markdown.rb +5 -5
  94. data/lib/aspera/nagios.rb +8 -8
  95. data/lib/aspera/node_simulator.rb +33 -27
  96. data/lib/aspera/oauth/base.rb +4 -5
  97. data/lib/aspera/oauth/boot.rb +3 -3
  98. data/lib/aspera/oauth/factory.rb +5 -5
  99. data/lib/aspera/oauth/jwt.rb +5 -7
  100. data/lib/aspera/oauth/web.rb +4 -4
  101. data/lib/aspera/persistency_action_once.rb +4 -4
  102. data/lib/aspera/persistency_folder.rb +8 -8
  103. data/lib/aspera/preview/file_types.rb +4 -4
  104. data/lib/aspera/preview/generator.rb +49 -46
  105. data/lib/aspera/preview/options.rb +3 -2
  106. data/lib/aspera/preview/terminal.rb +5 -5
  107. data/lib/aspera/preview/utils.rb +65 -18
  108. data/lib/aspera/products/connect.rb +2 -2
  109. data/lib/aspera/products/desktop.rb +1 -1
  110. data/lib/aspera/products/other.rb +2 -2
  111. data/lib/aspera/products/transferd.rb +3 -3
  112. data/lib/aspera/proxy_auto_config.rb +10 -10
  113. data/lib/aspera/rest.rb +149 -115
  114. data/lib/aspera/rest_error_analyzer.rb +3 -3
  115. data/lib/aspera/rest_list.rb +10 -9
  116. data/lib/aspera/schema/IBM Aspera Node API-4.4.6.yaml +6232 -0
  117. data/lib/aspera/schema/IBM Aspera faspio Gateway API-1.0.0.yaml +249 -0
  118. data/lib/aspera/schema/IBM Aspera on Cloud API-0.2.6-enhanced.yaml +39 -0
  119. data/lib/aspera/schema/IBM_Aspera_Shares.yaml +4653 -0
  120. data/lib/aspera/schema/documentation.rb +17 -11
  121. data/lib/aspera/schema/reader.rb +78 -4
  122. data/lib/aspera/schema/registry.rb +26 -7
  123. data/lib/aspera/secret_hider.rb +5 -5
  124. data/lib/aspera/ssh.rb +64 -31
  125. data/lib/aspera/ssl.rb +3 -3
  126. data/lib/aspera/sync/database.rb +2 -2
  127. data/lib/aspera/sync/operations.rb +13 -13
  128. data/lib/aspera/temp_file_manager.rb +24 -4
  129. data/lib/aspera/transfer/faux_file.rb +2 -2
  130. data/lib/aspera/transfer/parameters.rb +15 -15
  131. data/lib/aspera/transfer/resumer.rb +9 -9
  132. data/lib/aspera/transfer/spec.rb +1 -1
  133. data/lib/aspera/transfer/spec.schema.yaml +1 -0
  134. data/lib/aspera/uri_reader.rb +52 -15
  135. data/lib/aspera/web_auth.rb +6 -5
  136. data/lib/aspera/web_server_simple.rb +9 -9
  137. data/lib/aspera/yaml.rb +4 -4
  138. data.tar.gz.sig +0 -0
  139. metadata +10 -4
  140. metadata.gz.sig +0 -0
  141. data/lib/aspera/cli/plugins/faspex.rb +0 -576
  142. data/lib/aspera/transfer/uri.rb +0 -56
@@ -102,8 +102,8 @@ module Aspera
102
102
  # @param destination_folder [String] Base folder
103
103
  # @param fld [Array] List of fields of package
104
104
  def unique_folder(package_info, destination_folder, fld: nil, seq: false, opt: false)
105
- Aspera.assert_array_all(fld, String, type: BadArgument){'fld'}
106
- Aspera.assert_values(fld.length, [1, 2]){'fld length'}
105
+ Aspera.assert_array_all(fld, String, type: BadArgument) { 'fld' }
106
+ Aspera.assert_values(fld.length, [1, 2]) { 'fld length' }
107
107
  folder = Environment.instance.sanitized_filename(package_info[fld[0]])
108
108
  if seq
109
109
  folder = next_available_folder(folder, always: !opt)
@@ -113,6 +113,71 @@ module Aspera
113
113
  end
114
114
  File.join(destination_folder, folder)
115
115
  end
116
+
117
+ # @return [String] AoC REST path for an admin resource type
118
+ def aoc_res_path(res)
119
+ cfg = ADMIN_OBJECT_CONFIG.fetch(res, {})
120
+ return cfg[:path] if cfg[:path]
121
+ "#{res}s".gsub(/ys$/, 'ies')
122
+ end
123
+
124
+ # @return [Hash] {path:, ops:, id_result:, require_ws_id:, list_fields:, schema:, query_component:}
125
+ def aoc_res_cfg(res)
126
+ cfg = ADMIN_OBJECT_CONFIG.fetch(res, {})
127
+ path = aoc_res_path(res)
128
+ ops = cfg[:ops] || (Base::Operations::ALL + (cfg[:extra_ops] || []))
129
+ schema = cfg[:create_schema] == false ? nil : Schema::Registry.req_body(Schema::Registry::AOC, "#{path}.post")
130
+ {
131
+ path: path,
132
+ ops: ops,
133
+ id_result: cfg[:id_result] || 'id',
134
+ require_ws_id: cfg[:require_ws_id] || false,
135
+ list_fields: cfg.key?(:list_fields) ? cfg[:list_fields] : %w[id name],
136
+ schema: schema,
137
+ query_component: Schema::Registry::AOC
138
+ }
139
+ end
140
+
141
+ # DSL helper: register the 5 short_link leaf commands under the given parent path
142
+ # and define the corresponding action methods on `base`.
143
+ # @param base [Class] the plugin class
144
+ # @param parent_path [Array<Symbol>] full path ending with :short_link
145
+ def register_short_link_commands(base, parent_path)
146
+ base.commands_under(parent_path) do
147
+ base.command(
148
+ :create, description: 'Create',
149
+ arguments: [{name: :custom_data, type: Hash, mandatory: false, default: {}}]
150
+ )
151
+ base.command(
152
+ :modify, description: 'Modify',
153
+ arguments: [{name: :short_link_id, type: :identifier}, {name: :custom_data, type: Hash, mandatory: false, default: {}}]
154
+ )
155
+ base.command(:list, description: 'List short links')
156
+ base.command(
157
+ :show, description: 'Show a short link',
158
+ arguments: [{name: :short_link_id, type: :identifier}]
159
+ )
160
+ base.command(
161
+ :delete, description: 'Delete a short link',
162
+ arguments: [{name: :short_link_id, type: :identifier}]
163
+ )
164
+ end
165
+ base.define_action_method(parent_path + [:create]) do |custom_data: {}, **ctx|
166
+ sl_exec_create(custom_data, **ctx)
167
+ end
168
+ base.define_action_method(parent_path + [:list]) do |**ctx|
169
+ sl_exec_list(**sl_fetch_list(**ctx))
170
+ end
171
+ base.define_action_method(parent_path + [:show]) do |**ctx|
172
+ sl_exec_show(**sl_fetch_list(**ctx))
173
+ end
174
+ base.define_action_method(parent_path + [:delete]) do |**ctx|
175
+ sl_exec_delete(**sl_fetch_list(**ctx), **ctx)
176
+ end
177
+ base.define_action_method(parent_path + [:modify]) do |custom_data: {}, **ctx|
178
+ sl_exec_modify(custom_data, **sl_fetch_list(**ctx), **ctx)
179
+ end
180
+ end
116
181
  end
117
182
 
118
183
  # @param wizard [Wizard] The wizard object
@@ -133,7 +198,7 @@ module Aspera
133
198
  test_args: 'organization'
134
199
  }
135
200
  end
136
- options.declare(:use_generic_client, description: 'Wizard: AoC: use global or org specific jwt client id', allowed: Allowed::TYPES_BOOLEAN, default: Api::AoC.saas_url?(app_url))
201
+ options.declare(:use_generic_client, description: 'Wizard: AoC: use global or org specific jwt client id', allowed: Type::BOOLEAN, default: Api::AoC.saas_url?(app_url))
137
202
  options.parse_options!
138
203
  # make username mandatory for jwt, this triggers interactive input
139
204
  wiz_username = options.get_option(:username, mandatory: true)
@@ -203,9 +268,11 @@ module Aspera
203
268
 
204
269
  option :workspace, description: 'Name of workspace', allowed: [String, NilClass], default: Api::AoC::DEFAULT_WORKSPACE
205
270
  option :new_user_option, description: 'New user creation option for unknown package recipients', allowed: [Hash, NilClass]
206
- option :validate_metadata, description: 'Validate shared inbox metadata', allowed: Allowed::TYPES_BOOLEAN, default: true
271
+ option :validate_metadata, description: 'Validate shared inbox metadata', allowed: Type::BOOLEAN, default: true
207
272
  option :package_folder, schema: Schema::Registry::PACKAGE_FOLDER_OPTIONS
208
273
 
274
+ use_options Node
275
+
209
276
  def initialize(**_)
210
277
  super
211
278
  @cache_workspace_info = nil
@@ -213,8 +280,6 @@ module Aspera
213
280
  @cache_api_aoc = nil
214
281
  @scope = Api::AoC::Scope::USER
215
282
  options.parse_options!
216
- # add node plugin options (for manual)
217
- Node.declare_options(options)
218
283
  end
219
284
 
220
285
  # Change API scope for subsequent calls, re-instantiate API object
@@ -244,7 +309,7 @@ module Aspera
244
309
  case m[:field]
245
310
  when 'name' then api.ws_ids[:name] = m[:value]
246
311
  when 'id' then api.ws_ids[:id] = m[:value]
247
- else Aspera.error_unexpected_value(m[:field]){'workspace selector: only `name` or `id`'}
312
+ else Aspera.error_unexpected_value(m[:field]) { 'workspace selector: only `name` or `id`' }
248
313
  end
249
314
  else
250
315
  api.ws_ids[:name] = workspace
@@ -292,7 +357,7 @@ module Aspera
292
357
  # @return [String] identifier
293
358
  def get_resource_id_from_args(resource_class_path)
294
359
  return options.instance_identifier do |field, value|
295
- Aspera.assert_values(field, ['name'], type: BadArgument){'selector field'}
360
+ Aspera.assert_values(field, ['name'], type: BadArgument) { 'selector field' }
296
361
  aoc_api.lookup_with_q(resource_class_path, value: value)['id']
297
362
  end
298
363
  end
@@ -324,7 +389,7 @@ module Aspera
324
389
  def resolve_dropbox_name_default_ws_id(query)
325
390
  if query.key?('dropbox_name')
326
391
  # convenience: specify name instead of id
327
- Aspera.assert(!query.key?('dropbox_id'), type: BadArgument){'Use field dropbox_name or dropbox_id, not both'}
392
+ Aspera.assert(!query.key?('dropbox_id'), type: BadArgument) { 'Use field dropbox_name or dropbox_id, not both' }
328
393
  # TODO : craft a query that looks for dropbox only in current workspace
329
394
  query['dropbox_id'] = aoc_api.lookup_with_q('dropboxes', value: query.delete('dropbox_name'))['id']
330
395
  end
@@ -333,26 +398,34 @@ module Aspera
333
398
  query['exclude_dropbox_packages'] = !query.key?('dropbox_id') unless query.key?('exclude_dropbox_packages')
334
399
  end
335
400
 
336
- # List all packages according to `query` option.
337
- # @return [Hash] {items,total} with all packages according to combination of user's query and default query
401
+ # List all packages from the API using the current `--query` option.
402
+ # The special `max` key is extracted *before* the API call and returned separately,
403
+ # so callers that apply a post-API filter (e.g. once_only) can enforce the limit
404
+ # after filtering rather than before.
405
+ # @return [Array(Hash, Integer, nil)] [{items:,total:} paging result, max (or nil)]
338
406
  def list_all_packages_with_query
339
- query = query_read_delete(default: {})
340
- Aspera.assert_type(query, Hash){'query'}
341
- PACKAGE_RECEIVED_BASE_QUERY.each{ |k, v| query[k] = v unless query.key?(k)}
407
+ query = query_read_delete(default: {}, schema: Schema::Registry.query_params(Schema::Registry::AOC, 'packages'))
408
+ Aspera.assert_type(query, Hash) { 'query' }
409
+ PACKAGE_RECEIVED_BASE_QUERY.each { |k, v| query[k] = v unless query.key?(k) }
342
410
  resolve_dropbox_name_default_ws_id(query)
343
- return aoc_api.read_with_paging('packages', query.compact)
411
+ # Extract `max` before paging so callers can apply it after post-API filtering
412
+ max_items = query.delete(RestList::MAX_ITEMS)&.to_i
413
+ return aoc_api.read_with_paging('packages', query.compact), max_items
344
414
  end
345
415
 
346
416
  FILES_COMMANDS = (Node::COMMANDS_GEN4 + %i[transfer]).freeze
347
417
 
348
- # Execute a node gen4 command starting at given node and file IDs
349
- # @param command_repo [Symbol] Command to execute
418
+ # Execute a node gen4 command starting at given node and file IDs.
419
+ # Arguments already resolved by the DSL (e.g. path:) are forwarded via +resolved_args+
420
+ # and injected into the dispatch context so node.rb does not re-consume them from the CLI.
421
+ # @param command_repo [Symbol] Command to execute (from Node::COMMANDS_GEN4 or :transfer)
350
422
  # @param node_id [String] Node identifier
351
- # @param file_id [String] Root file id for the operation. If nil, uses AK root file id.
352
- # @param scope [String] node scope (Node::SCOPE_<USER|ADMIN>), or nil (requires secret)
353
- # @param transfer_direction [Symbol] :push or :pull (only for command_repo == :transfer)
354
- # @param transfer_source [String] source folder (only for command_repo == :transfer)
355
- def execute_nodegen4_command(command_repo, node_id, file_id: nil, scope: nil, transfer_direction: nil, transfer_source: nil)
423
+ # @param file_id [String, nil] Root file id; if nil, the AK root file id is used
424
+ # @param scope [String, nil] node scope (Node::Scope::USER/ADMIN), or nil (requires secret)
425
+ # @param transfer_direction [Symbol, nil] :push or :pull (only for command_repo == :transfer)
426
+ # @param transfer_source [String, nil] source folder (only for command_repo == :transfer)
427
+ # @param resolved_args [Hash] already-resolved CLI arguments (e.g. path:) forwarded to dispatch
428
+ def execute_nodegen4_command(command_repo, node_id, file_id: nil, scope: nil, transfer_direction: nil, transfer_source: nil, **resolved_args)
356
429
  top_node_api = aoc_api.node_api_from(
357
430
  node_id: node_id,
358
431
  scope: scope,
@@ -364,8 +437,8 @@ module Aspera
364
437
  when *Node::COMMANDS_GEN4
365
438
  # For permission: the handler consumes the path first then re-dispatches to sub-commands.
366
439
  # Calling dispatch_from_registry with skip_setup would bypass path consumption and fail.
367
- return node_plugin.send(:"action_access_keys_do_#{command_repo}", do_root_file_id: file_id) if command_repo.eql?(:permission)
368
- return node_plugin.dispatch_from_registry([:access_keys, :do, command_repo], {do_root_file_id: file_id}, skip_setup: true)
440
+ return node_plugin.send(:"action_access_keys_do_#{command_repo}", do_root_file_id: file_id, **resolved_args) if command_repo.eql?(:permission)
441
+ return node_plugin.dispatch_from_registry([:access_keys, :do, command_repo], {do_root_file_id: file_id, **resolved_args}, skip_setup: true)
369
442
  when :transfer
370
443
  # client side is agent
371
444
  # server side is transfer server
@@ -403,7 +476,7 @@ module Aspera
403
476
  client_direction,
404
477
  add_ts
405
478
  )))
406
- else Aspera.error_unexpected_value(command_repo){'command'}
479
+ else Aspera.error_unexpected_value(command_repo) { 'command' }
407
480
  end
408
481
  Aspera.error_unreachable_line
409
482
  end
@@ -427,7 +500,7 @@ module Aspera
427
500
  group_membership: {list_fields: %w[id group_id member_type member_id], create_schema: false},
428
501
  kms_profile: {path: 'integrations/kms_profiles', create_schema: false},
429
502
  network_policy: {list_fields: nil},
430
- node: {list_fields: %w[id name host access_key], extra_ops: %i[do bearer_token], extra_op_args: {bearer_token: [{name: :scope, mandatory: false, default: nil}]}},
503
+ node: {list_fields: %w[id name host access_key], extra_ops: %i[do bearer_token update_status], extra_op_args: {bearer_token: [{name: :scope, mandatory: false, default: nil}]}},
431
504
  operation: {list_fields: %w[id type status created_at updated_at workspace_id user_id workspace_membership_id group_membership_id], ops: %i[list show modify]},
432
505
  organization: {singleton: true},
433
506
  package: {},
@@ -440,73 +513,6 @@ module Aspera
440
513
  }.freeze
441
514
  private_constant :ADMIN_OBJECT_CONFIG
442
515
 
443
- class << self
444
- # @return [String] AoC REST path for an admin resource type
445
- def aoc_res_path(res)
446
- cfg = ADMIN_OBJECT_CONFIG.fetch(res, {})
447
- return cfg[:path] if cfg[:path]
448
- "#{res}s".gsub(/ys$/, 'ies')
449
- end
450
-
451
- # @return [Hash] {path:, ops:, id_result:, require_ws_id:, list_fields:, schema:, query_component:}
452
- def aoc_res_cfg(res)
453
- cfg = ADMIN_OBJECT_CONFIG.fetch(res, {})
454
- path = aoc_res_path(res)
455
- ops = cfg[:ops] || (Base::Operations::ALL + (cfg[:extra_ops] || []))
456
- schema = cfg[:create_schema] == false ? nil : Schema::Registry.req_body(Schema::Registry::AOC, "#{path}.post")
457
- {
458
- path: path,
459
- ops: ops,
460
- id_result: cfg[:id_result] || 'id',
461
- require_ws_id: cfg[:require_ws_id] || false,
462
- list_fields: cfg.key?(:list_fields) ? cfg[:list_fields] : %w[id name],
463
- schema: schema,
464
- query_component: Schema::Registry::AOC
465
- }
466
- end
467
-
468
- # DSL helper: register the 5 short_link leaf commands under the given parent path
469
- # and define the corresponding action methods on `base`.
470
- # @param base [Class] the plugin class
471
- # @param parent_path [Array<Symbol>] full path ending with :short_link
472
- def register_short_link_commands(base, parent_path)
473
- base.commands_under(parent_path) do
474
- base.command(
475
- :create, description: 'Create',
476
- arguments: [{name: :custom_data, type: Hash, mandatory: false, default: {}}]
477
- )
478
- base.command(
479
- :modify, description: 'Modify',
480
- arguments: [{name: :short_link_id, type: :identifier}, {name: :custom_data, type: Hash, mandatory: false, default: {}}]
481
- )
482
- base.command(:list, description: 'List short links')
483
- base.command(
484
- :show, description: 'Show a short link',
485
- arguments: [{name: :short_link_id, type: :identifier}]
486
- )
487
- base.command(
488
- :delete, description: 'Delete a short link',
489
- arguments: [{name: :short_link_id, type: :identifier}]
490
- )
491
- end
492
- base.define_action_method(parent_path + [:create]) do |custom_data: {}, **ctx|
493
- sl_exec_create(custom_data, **ctx)
494
- end
495
- base.define_action_method(parent_path + [:list]) do |**ctx|
496
- sl_exec_list(**sl_fetch_list(**ctx))
497
- end
498
- base.define_action_method(parent_path + [:show]) do |**ctx|
499
- sl_exec_show(**sl_fetch_list(**ctx))
500
- end
501
- base.define_action_method(parent_path + [:delete]) do |**ctx|
502
- sl_exec_delete(**sl_fetch_list(**ctx), **ctx)
503
- end
504
- base.define_action_method(parent_path + [:modify]) do |custom_data: {}, **ctx|
505
- sl_exec_modify(custom_data, **sl_fetch_list(**ctx), **ctx)
506
- end
507
- end
508
- end
509
-
510
516
  # Instance delegators so instance methods can call aoc_res_path/aoc_res_cfg without self.class.
511
517
  def aoc_res_path(res) = self.class.aoc_res_path(res)
512
518
  def aoc_res_cfg(res) = self.class.aoc_res_cfg(res)
@@ -514,7 +520,7 @@ module Aspera
514
520
  # Known fixed set of AoC application types (verified against API: activity, automation, files, packages)
515
521
  APP_TYPES = %i[activity automation files packages].freeze
516
522
 
517
- ADMIN_ACTIONS = %i[bearer_token application ats usage_reports analytics subscription auth_providers].concat(ADMIN_OBJECTS).freeze
523
+ ADMIN_ACTIONS = (%i[bearer_token application ats usage_reports analytics subscription auth_providers] + ADMIN_OBJECTS).freeze
518
524
 
519
525
  # Build analytics REST API (shared by action_admin_analytics_*)
520
526
  def build_analytics_api
@@ -575,8 +581,8 @@ module Aspera
575
581
 
576
582
  def reject_packages_from_persistency(all_packages, skip_ids_persistency)
577
583
  return if skip_ids_persistency.nil?
578
- skip_package = skip_ids_persistency.data.to_h{ |i| [i, true]}
579
- all_packages.reject!{ |pkg| skip_package[pkg['id']]}
584
+ skip_package = skip_ids_persistency.data.to_h { |i| [i, true] }
585
+ all_packages.reject! { |pkg| skip_package[pkg['id']] }
580
586
  end
581
587
 
582
588
  # --- DSL command declarations ---
@@ -591,31 +597,23 @@ module Aspera
591
597
  end
592
598
  )
593
599
  command :bearer_token, description: 'Display bearer token',
594
- action: ->{Result::Text.new(aoc_api.oauth.authorization)}
600
+ action: -> { Result::Text.new(aoc_api.oauth.authorization) }
595
601
  command :organization, description: 'Show organization info',
596
- action: ->{Result::SingleObject.new(aoc_api.read('organization'))}
602
+ action: -> { Result::SingleObject.new(aoc_api.read('organization')) }
597
603
  command :tier_restrictions, description: 'Show tier restrictions',
598
- action: ->{Result::SingleObject.new(aoc_api.read('tier_restrictions'))}
604
+ action: -> { Result::SingleObject.new(aoc_api.read('tier_restrictions')) }
599
605
  command :user, description: 'User commands'
600
606
  command :packages, description: 'Package commands', setup: :setup_workspace_display
601
607
  command :files, description: 'Files commands (workspace-aware)', setup: :setup_workspace_display
602
608
  command :admin, description: 'Administration commands', setup: :setup_admin_scope
603
- commands_under(:admin) do
609
+ commands_under :admin do
604
610
  command :bearer_token, description: 'Show admin bearer token',
605
- action: ->{Result::Text.new(aoc_api.oauth.authorization)}
611
+ action: -> { Result::Text.new(aoc_api.oauth.authorization) }
606
612
  command :application, description: 'Manage applications'
607
- command(
608
- :ats, description: 'Manage ATS (Aspera Transfer Service)',
609
- action: lambda do
610
- ats_api = Rest.new(**aoc_api.params.deep_merge({
611
- base_url: "#{aoc_api.base_url}/admin/ats/pub/v1",
612
- auth: {params: {scope: Api::AoC::Scope::ADMIN_USER}}
613
- }))
614
- Ats.new(context: context, api: ats_api).execute_action
615
- end
616
- )
613
+ command :ats, description: 'Manage ATS (Aspera Transfer Service)',
614
+ delegate_instance: :build_ats_plugin, delegates_to: []
617
615
  command :usage_reports, description: 'List usage reports',
618
- action: ->{result_list('usage_reports', base_query: workspace_id_hash)}
616
+ action: -> { result_list('usage_reports', base_query: workspace_id_hash) }
619
617
  command :auth_providers, description: 'Manage auth providers'
620
618
  command :subscription, description: 'Show subscription info'
621
619
  command :analytics, description: 'Query analytics'
@@ -624,7 +622,7 @@ module Aspera
624
622
  op_setup = cfg[:op_setup] || {}
625
623
  extra_op_args = cfg[:extra_op_args] || {}
626
624
  is_singleton = cfg[:singleton]
627
- id_arg_spec = is_singleton ? [] : [{name: :res_id, type: :identifier, lookup: :"lookup_aoc_#{res}_id"}]
625
+ id_arg_spec = is_singleton ? [] : [{name: :"#{res}_id", type: :identifier, lookup: :"lookup_aoc_#{res}_id"}]
628
626
  ops = if cfg[:ops]
629
627
  cfg[:ops]
630
628
  elsif is_singleton
@@ -632,8 +630,8 @@ module Aspera
632
630
  else
633
631
  Operations::ALL + (cfg[:extra_ops] || [])
634
632
  end
635
- command(res, description: "Manage #{res.to_s.tr('_', ' ')}")
636
- commands_under([:admin, res]) do
633
+ command res, description: "Manage #{res.to_s.tr('_', ' ')}"
634
+ commands_under res do
637
635
  ops.each do |op|
638
636
  extra_setup = op_setup[op]
639
637
  base_attrs = extra_setup ? {setup: extra_setup} : {}
@@ -657,13 +655,13 @@ module Aspera
657
655
  c = aoc_res_cfg(res)
658
656
  merged = merged.merge(query_schema: Schema::Registry.query_params(c[:query_component], c[:path]))
659
657
  end
660
- command(op, description: op.to_s.tr('_', ' ').capitalize, **merged)
658
+ command op, description: op.to_s.tr('_', ' ').capitalize, **merged
661
659
  end
662
660
  end
663
661
  end
664
662
  end
665
663
  # admin > workspace > shared_folder sub-tree
666
- commands_under(%i[admin workspace shared_folder]) do
664
+ commands_under %i[admin workspace shared_folder] do
667
665
  command :list, description: 'List shared folders'
668
666
  command :node, description: 'Execute node command on shared folder',
669
667
  arguments: [{name: :sf_id, type: :identifier}],
@@ -672,20 +670,30 @@ module Aspera
672
670
  arguments: [{name: :sf_id, type: :identifier}],
673
671
  setup: :setup_admin_workspace_shared_folder_member
674
672
  end
675
- # admin > workspace > shared_folder > node sub-tree (FILES_COMMANDS)
676
- commands_under(%i[admin workspace shared_folder node]) do
677
- FILES_COMMANDS.each{ |c| command(c, description: c.to_s.tr('_', ' ').capitalize)}
673
+ # admin > workspace > shared_folder > node sub-tree (Gen4 commands)
674
+ commands_under %i[admin workspace shared_folder node] do
675
+ command :transfer, description: 'Transfer files (node-to-node)'
676
+ command :permission, description: 'Manage permissions'
677
+ command :sync, description: 'Synchronize folders'
678
+ Node::COMMANDS_GEN4_SPEC.each do |cmd, spec|
679
+ command cmd, **spec
680
+ end
678
681
  end
679
- commands_under(%i[admin workspace shared_folder member]) do
682
+ commands_under %i[admin workspace shared_folder member] do
680
683
  command :list, description: 'List members of a shared folder'
681
684
  end
682
685
  # admin > workspace > dropbox sub-tree
683
- commands_under(%i[admin workspace dropbox]) do
686
+ commands_under %i[admin workspace dropbox] do
684
687
  command :list, description: 'List dropboxes in workspace'
685
688
  end
686
- # admin > node > do sub-tree (FILES_COMMANDS)
687
- commands_under(%i[admin node do]) do
688
- FILES_COMMANDS.each{ |c| command(c, description: c.to_s.tr('_', ' ').capitalize)}
689
+ # admin > node > do sub-tree (Gen4 commands)
690
+ commands_under %i[admin node do] do
691
+ command :transfer, description: 'Transfer files (node-to-node)'
692
+ command :permission, description: 'Manage permissions'
693
+ command :sync, description: 'Synchronize folders'
694
+ Node::COMMANDS_GEN4_SPEC.each do |cmd, spec|
695
+ command cmd, **spec
696
+ end
689
697
  end
690
698
  # admin > user > preferences|notifications sub-trees
691
699
  %i[preferences notifications].each do |pref|
@@ -695,12 +703,12 @@ module Aspera
695
703
  arguments: [{name: :properties, type: Hash}]
696
704
  end
697
705
  end
698
- commands_under(%i[admin auth_providers]) do
706
+ commands_under %i[admin auth_providers] do
699
707
  command :list, description: 'List auth providers',
700
- action: ->{result_list('admin/auth_providers')}
701
- command :update, description: 'Update auth provider', action: ->{Aspera.error_not_implemented}
708
+ action: -> { result_list('admin/auth_providers') }
709
+ command :update, description: 'Update auth provider', action: -> { Aspera.error_not_implemented }
702
710
  end
703
- commands_under(%i[admin subscription]) do
711
+ commands_under %i[admin subscription] do
704
712
  command :account, description: 'Show subscription account'
705
713
  command :usage, description: 'Show subscription usage',
706
714
  arguments: [
@@ -709,7 +717,7 @@ module Aspera
709
717
  {name: :end_date, mandatory: false, default: nil}
710
718
  ]
711
719
  end
712
- commands_under(%i[admin analytics]) do
720
+ commands_under %i[admin analytics] do
713
721
  command :application_events, description: 'List application events'
714
722
  command :transfers, description: 'List transfer events',
715
723
  arguments: [
@@ -724,9 +732,9 @@ module Aspera
724
732
  ]
725
733
  end
726
734
  # application sub-commands
727
- commands_under(%i[admin application]) do
735
+ commands_under %i[admin application] do
728
736
  command :types, description: 'List application types',
729
- action: ->{Result::ObjectList.new(aoc_api.read('admin/apps'))}
737
+ action: -> { Result::ObjectList.new(aoc_api.read('admin/apps')) }
730
738
  command :settings, description: 'Manage per-app-type settings'
731
739
  command :instance, description: 'Manage app instances'
732
740
  command :membership, description: 'Manage app memberships'
@@ -734,12 +742,12 @@ module Aspera
734
742
  APP_SETTINGS_PATH = %i[admin application settings].freeze
735
743
  APP_INSTANCE_PATH = %i[admin application instance].freeze
736
744
  private_constant :APP_SETTINGS_PATH, :APP_INSTANCE_PATH
737
- commands_under(APP_SETTINGS_PATH) do
745
+ commands_under APP_SETTINGS_PATH do
738
746
  APP_TYPES.each do |app_type|
739
- command(app_type, description: "Settings for #{app_type} app")
740
- commands_under(APP_SETTINGS_PATH + [app_type]) do
747
+ command app_type, description: "Settings for #{app_type} app"
748
+ commands_under app_type do
741
749
  command :show, description: "Show #{app_type} settings",
742
- action: ->{Result::SingleObject.new(aoc_api.read("/apps/#{app_type}/settings"))}
750
+ action: -> { Result::SingleObject.new(aoc_api.read("/apps/#{app_type}/settings")) }
743
751
  command(
744
752
  :modify, description: "Modify #{app_type} settings",
745
753
  arguments: [{name: :properties, type: Hash}],
@@ -751,7 +759,7 @@ module Aspera
751
759
  end
752
760
  end
753
761
  end
754
- commands_under(APP_INSTANCE_PATH) do
762
+ commands_under APP_INSTANCE_PATH do
755
763
  command(:list, description: 'List app instances', action: lambda do
756
764
  result_list(
757
765
  'admin/apps_new',
@@ -760,64 +768,57 @@ module Aspera
760
768
  )
761
769
  end)
762
770
  APP_TYPES.each do |app_type|
763
- command(app_type, description: "Show or modify a #{app_type} instance")
764
- commands_under(APP_INSTANCE_PATH + [app_type]) do
771
+ command app_type, description: "Show or modify a #{app_type} instance"
772
+ commands_under app_type do
765
773
  command :show, description: "Show a #{app_type} instance",
766
- arguments: [{name: :res_id, type: :identifier}]
774
+ arguments: [{name: :"#{app_type}_id", type: :identifier}]
767
775
  command :modify, description: "Modify a #{app_type} instance",
768
- arguments: [{name: :res_id, type: :identifier}, {name: :properties, type: Hash}]
776
+ arguments: [{name: :"#{app_type}_id", type: :identifier}, {name: :properties, type: Hash}]
769
777
  end
770
778
  end
771
779
  end
772
- commands_under(%i[admin application membership]) do
773
- command :list, description: 'List app memberships',
774
- action: ->{result_list('apps/app_memberships')}
775
- command :show, description: 'Show an app membership',
776
- arguments: [{name: :res_id, type: :identifier}]
777
- command :delete, description: 'Delete an app membership',
778
- arguments: [{name: :res_id, type: :identifier}]
779
- command :create, description: 'Create an app membership',
780
- arguments: [{name: :membership, type: Hash}]
780
+ commands_under %i[admin application membership] do
781
+ command :list, description: 'List app memberships', action: -> { result_list('apps/app_memberships') }
782
+ command :show, description: 'Show an app membership', arguments: [{name: :membership_id, type: :identifier}]
783
+ command :delete, description: 'Delete an app membership', arguments: [{name: :membership_id, type: :identifier}]
784
+ command :create, description: 'Create an app membership', arguments: [{name: :membership, type: Hash}]
781
785
  end
782
786
  command :automation, description: 'Automation commands (BETA)', setup: :setup_automation_api
783
787
  command :gateway, description: 'Start AoC Faspex4 gateway',
784
788
  arguments: [{name: :parameters, type: Hash, mandatory: false, default: {}}]
785
789
 
786
790
  # user sub-commands
787
- commands_under(:user) do
788
- command :workspaces, description: 'Workspace commands'
789
- command :profile, description: 'User profile commands'
791
+ commands_under :user do
792
+ commands_under :workspaces, description: "User's workspaces" do
793
+ command :list, description: 'List workspaces', action: -> { result_list('workspaces', fields: %w[id name]) }
794
+ command :current, description: 'Show current workspace', action: -> { Result::SingleObject.new(aoc_api.workspace_info) }
795
+ end
796
+ # command :profile, description: 'User profile commands'
797
+ commands_under :profile, description: "Manager user's profile" do
798
+ command :show, description: 'Show user profile', action: -> { Result::SingleObject.new(aoc_api.current_user_info(exception: true)) }
799
+ command(
800
+ :modify, description: 'Modify user profile',
801
+ arguments: [{name: :properties, type: Hash}],
802
+ action: lambda do |properties:, **|
803
+ aoc_api.update("users/#{aoc_api.current_user_info(exception: true)['id']}", properties)
804
+ Result::Status.new('modified')
805
+ end
806
+ )
807
+ end
790
808
  command :preferences, description: 'User interaction preferences'
791
809
  command :notifications, description: 'Notification preferences'
792
810
  command :contacts, description: 'Manage contacts'
793
- entity_command :settings, api: :aoc_api, entity: 'client_settings', description: 'Manage client settings'
794
- end
795
- # user > contacts sub-commands (same CRUD as admin > contact)
796
- commands_under(%i[user contacts]) do
797
- Operations::ALL.each{ |op| command(op, description: op.to_s.capitalize)}
798
- end
799
-
800
- commands_under(%i[user workspaces]) do
801
- command :list, description: 'List workspaces',
802
- action: ->{result_list('workspaces', fields: %w[id name])}
803
- command :current, description: 'Show current workspace',
804
- action: ->{Result::SingleObject.new(aoc_api.workspace_info)}
805
- end
806
-
807
- commands_under(%i[user profile]) do
808
- command :show, description: 'Show user profile',
809
- action: ->{Result::SingleObject.new(aoc_api.current_user_info(exception: true))}
810
- command(
811
- :modify, description: 'Modify user profile',
812
- arguments: [{name: :properties, type: Hash}],
813
- action: lambda do |properties:, **|
814
- aoc_api.update("users/#{aoc_api.current_user_info(exception: true)['id']}", properties)
815
- Result::Status.new('modified')
816
- end
817
- )
811
+ # user > contacts sub-commands (same CRUD as admin > contact)
812
+ commands_under %i[contacts] do
813
+ Operations::ALL.each { |op| command op, description: op.to_s.capitalize }
814
+ end
815
+ command :settings, description: 'Manage client settings'
816
+ commands_under %i[settings] do
817
+ crud_commands api: :aoc_api, entity: 'client_settings', name: 'client setting'
818
+ end
818
819
  end
819
820
 
820
- commands_under(%i[user preferences]) do
821
+ commands_under %i[user preferences] do
821
822
  command(
822
823
  :show, description: 'Show user preferences',
823
824
  action: lambda do
@@ -836,7 +837,7 @@ module Aspera
836
837
  )
837
838
  end
838
839
 
839
- commands_under(%i[user notifications]) do
840
+ commands_under %i[user notifications] do
840
841
  command(
841
842
  :show, description: 'Show notification preferences',
842
843
  action: lambda do
@@ -856,31 +857,31 @@ module Aspera
856
857
  end
857
858
 
858
859
  # packages sub-commands — instance commands consume package_id
859
- commands_under(:packages) do
860
- command :shared_inboxes, description: 'Shared inbox commands'
861
- command :send, description: 'Send a package', transfer_paths: :send,
860
+ commands_under :packages do
861
+ command :shared_inboxes, description: 'Shared inbox commands'
862
+ command :send, description: 'Send a package', transfer_paths: :send,
862
863
  arguments: [{name: :data, type: Hash, schema: Schema::Registry.req_body(Schema::Registry::AOC, 'packages.post')}]
863
- command :receive, description: 'Receive package(s)', aliases: [:recv], transfer_paths: :receive,
864
+ command :receive, description: 'Receive packages', aliases: [:recv], transfer_paths: :receive,
864
865
  arguments: [{name: :package_id, type: :identifier}]
865
- command :list, description: 'List packages'
866
- command :show, description: 'Show a package',
866
+ command :list, description: 'List packages'
867
+ command :show, description: 'Show a package',
867
868
  arguments: [{name: :package_id, type: :identifier}]
868
- command :delete, description: 'Delete package(s)',
869
+ command :delete, description: 'Delete packages',
869
870
  arguments: [{name: :package_id, type: :identifier}]
870
- command :modify, description: 'Modify a package',
871
+ command :modify, description: 'Modify a package',
871
872
  arguments: [{name: :package_id, type: :identifier}, {name: :data, type: Hash}]
872
873
  # Node Gen4 read-only actions on packages
873
874
  command :bearer_token_node, description: 'Show bearer token for package node',
874
875
  arguments: [{name: :package_id, type: :identifier}]
875
876
  command :node_info, description: 'Show node info for package',
876
877
  arguments: [{name: :package_id, type: :identifier}]
877
- command :browse, description: 'Browse package contents',
878
+ command :ls, description: 'List package contents', aliases: [:browse],
878
879
  arguments: [{name: :package_id, type: :identifier}]
879
880
  command :find, description: 'Find files in package',
880
881
  arguments: [{name: :package_id, type: :identifier}]
881
882
  end
882
883
 
883
- commands_under(%i[packages shared_inboxes]) do
884
+ commands_under %i[packages shared_inboxes] do
884
885
  command :list, description: 'List shared inboxes',
885
886
  action: (lambda do
886
887
  result_list(
@@ -891,7 +892,7 @@ module Aspera
891
892
  end)
892
893
  command :show, description: 'Show a shared inbox',
893
894
  arguments: [{name: :dropbox_id, type: :identifier, lookup: :lookup_aoc_dropbox_id}],
894
- action: ->(dropbox_id:, **){Result::SingleObject.new(aoc_api.read("dropboxes/#{dropbox_id}"))}
895
+ action: ->(dropbox_id:, **) { Result::SingleObject.new(aoc_api.read("dropboxes/#{dropbox_id}")) }
895
896
  command :short_link, description: 'Manage shared inbox short links',
896
897
  arguments: [{name: :link_type, allowed: %i[public private]}],
897
898
  setup: :setup_packages_short_link
@@ -899,61 +900,41 @@ module Aspera
899
900
  # packages > shared_inboxes > short_link sub-commands
900
901
  register_short_link_commands(self, %i[packages shared_inboxes short_link])
901
902
 
902
- # files sub-commands: all FILES_COMMANDS + :short_link
903
- # Declared dynamically after FILES_COMMANDS is available (class body evaluated after constants)
904
- commands_under(:files) do
903
+ # files sub-commands: AoC-specific commands + all Gen4 commands from COMMANDS_GEN4_SPEC
904
+ commands_under :files do
905
905
  command :short_link, description: 'Manage file short link',
906
906
  arguments: [{name: :folder_dest, type: String}, {name: :link_type, allowed: %i[public private]}],
907
907
  setup: :setup_files_short_link
908
908
  command :transfer, description: 'Transfer files (node-to-node)',
909
909
  arguments: [{name: :direction, allowed: %i[push pull]}, {name: :source_folder, type: String}]
910
- command :mkdir, description: 'Create folder'
911
- command :mklink, description: 'Create symbolic link'
912
- command :mkfile, description: 'Create file'
913
- command :rename, description: 'Rename entry'
914
- command :delete, description: 'Delete entry'
915
- command :upload, description: 'Upload files', transfer_paths: :send
916
- command :download, description: 'Download files', transfer_paths: :receive
917
- command :sync, description: 'Synchronize folders'
918
- command :cat, description: 'Show file contents'
919
- command :show, description: 'Show file info'
920
- command :modify, description: 'Modify file'
921
- command :permission, description: 'Manage permissions'
922
- command :thumbnail, description: 'Show file thumbnail'
923
- command :v3, description: 'Legacy v3 commands on files'
924
- command :bearer_token_node, description: 'Show bearer token for file node'
925
- command :node_info, description: 'Show node info for file'
926
- command :browse, description: 'Browse files'
927
- command :find, description: 'Find files'
910
+ command :permission, description: 'Manage permissions'
911
+ command :sync, description: 'Synchronize folders'
912
+ Node::COMMANDS_GEN4_SPEC.each do |cmd, spec|
913
+ command cmd, **spec
914
+ end
928
915
  end
929
916
  # files > short_link sub-commands
930
917
  register_short_link_commands(self, %i[files short_link])
931
918
 
932
919
  # automation sub-commands
933
- commands_under(:automation) do
934
- entity_command :instances, description: 'Manage workflow instances', api: :aoc_api, entity: 'workflow_instances'
935
- command :workflows, description: 'Manage workflows'
936
- end
937
-
938
- commands_under(%i[automation workflows]) do
939
- command :create, description: 'Create a workflow'
940
- command :list, description: 'List workflows'
941
- command :show, description: 'Show a workflow'
942
- command :modify, description: 'Modify a workflow'
943
- command :delete, description: 'Delete a workflow'
944
- command :launch, description: 'Launch a workflow',
945
- arguments: [{name: :wf_id, type: :identifier}],
946
- action: ->(wf_id:, **){Result::SingleObject.new(@automation_api.create("workflows/#{wf_id}/launch", {}))}
947
- command :action, description: 'Add action to workflow (TODO)'
948
- end
920
+ commands_under :automation do
921
+ commands_under :instances do
922
+ crud_commands api: :aoc_api, entity: 'workflow_instances', name: 'workflow instance'
923
+ end
949
924
 
950
- commands_under(%i[automation workflows action]) do
951
- command :list, description: 'List actions (TODO)',
952
- arguments: [{name: :wf_id, type: :identifier}]
953
- command :create, description: 'Create an action (TODO)',
954
- arguments: [{name: :wf_id, type: :identifier}]
955
- command :show, description: 'Show an action (TODO)',
956
- arguments: [{name: :wf_id, type: :identifier}]
925
+ commands_under :workflows do
926
+ crud_commands api: :@automation_api, entity: 'workflows'
927
+ command :launch, description: 'Launch a workflow',
928
+ arguments: [{name: :wf_id, type: :identifier}],
929
+ action: ->(wf_id:, **) { Result::SingleObject.new(@automation_api.create("workflows/#{wf_id}/launch", {})) }
930
+ commands_under :action, description: 'Add action to workflow (TODO)' do
931
+ %i[list create show].each do |cmd|
932
+ command cmd,
933
+ description: "#{cmd.capitalize} action (TODO)",
934
+ arguments: [{name: :wf_id, type: :identifier}]
935
+ end
936
+ end
937
+ end
957
938
  end
958
939
 
959
940
  # --- setup methods ---
@@ -1015,15 +996,16 @@ module Aspera
1015
996
  skip_ids_persistency = package_persistency
1016
997
  case ids_to_download
1017
998
  when SpecialValues::INIT
1018
- all_packages = list_all_packages_with_query[:items]
999
+ all_packages, = list_all_packages_with_query
1019
1000
  Aspera.assert(skip_ids_persistency, 'INIT requires option once_only')
1020
- skip_ids_persistency.data.clear.concat(all_packages.map{ |e| e['id']})
1001
+ skip_ids_persistency.data.clear.concat(all_packages[:items].map { |e| e['id'] })
1021
1002
  skip_ids_persistency.save
1022
1003
  return Result::Status.new("Initialized skip for #{skip_ids_persistency.data.count} package(s)")
1023
1004
  when SpecialValues::ALL
1024
- all_packages = list_all_packages_with_query[:items]
1025
- reject_packages_from_persistency(all_packages, skip_ids_persistency)
1026
- ids_to_download = all_packages.map{ |e| e['id']}
1005
+ all_packages, max_items = list_all_packages_with_query
1006
+ reject_packages_from_persistency(all_packages[:items], skip_ids_persistency)
1007
+ all_packages[:items] = all_packages[:items][0, max_items] if max_items
1008
+ ids_to_download = all_packages[:items].map { |e| e['id'] }
1027
1009
  formatter.display_status("Found #{ids_to_download.length} package(s).")
1028
1010
  else
1029
1011
  ids_to_download = [ids_to_download] unless ids_to_download.is_a?(Array)
@@ -1061,9 +1043,10 @@ module Aspera
1061
1043
 
1062
1044
  # packages > list
1063
1045
  def action_packages_list
1064
- result = list_all_packages_with_query
1046
+ result, max_items = list_all_packages_with_query
1065
1047
  skip_ids_persistency = package_persistency
1066
1048
  reject_packages_from_persistency(result[:items], skip_ids_persistency)
1049
+ result[:items] = result[:items][0, max_items] if max_items
1067
1050
  display_fields = PACKAGE_LIST_DEFAULT_FIELDS
1068
1051
  display_fields += ['workspace_id'] if aoc_api.workspace_info[:id].nil?
1069
1052
  Result::ObjectList.new(result[:items], fields: display_fields, total: result[:total])
@@ -1076,10 +1059,8 @@ module Aspera
1076
1059
 
1077
1060
  # packages > delete
1078
1061
  def action_packages_delete(package_id:, **)
1079
- is_bulk = options.get_option(:bulk)
1080
- items = package_id.is_a?(Array) ? package_id : [package_id]
1081
- Result.bulk(items, is_bulk: is_bulk, command: :delete, bfail: options.get_option(:bfail)) do |one_id|
1082
- Aspera.assert_type(one_id, String, Integer){'identifier'}
1062
+ bulk_result(package_id, command: :delete) do |one_id|
1063
+ Aspera.assert_type(one_id, String, Integer) { 'identifier' }
1083
1064
  aoc_api.delete("packages/#{one_id}")
1084
1065
  end
1085
1066
  end
@@ -1090,7 +1071,7 @@ module Aspera
1090
1071
  Result::Status.new('modified')
1091
1072
  end
1092
1073
 
1093
- # packages > bearer_token_node / node_info / browse / find
1074
+ # packages > bearer_token_node / node_info / ls / find
1094
1075
  # (NODE4_READ_ACTIONS dispatched by full path: action_packages_bearer_token_node, etc.)
1095
1076
  Node::NODE4_READ_ACTIONS.each do |action|
1096
1077
  define_action_method([:packages, action]) do |package_id:, **|
@@ -1134,12 +1115,12 @@ module Aspera
1134
1115
  created_data = shared_apifid.node_api.create('permissions', perm_data)
1135
1116
  aoc_api.permissions_send_event(event_data: created_data, app_info: shared_apifid.node_api.app_info)
1136
1117
  when :update
1137
- found = shared_apifid.node_api.read('permissions', {file_id: shared_apifid.file_id, inherited: false, access_type: 'user', access_id: id}).find{ |i| i['access_id'].eql?(id)}
1138
- Aspera.assert(!found.nil?, type: Error){"Short link not found: #{id}"}
1118
+ found = shared_apifid.node_api.read('permissions', {file_id: shared_apifid.file_id, inherited: false, access_type: 'user', access_id: id}).find { |i| i['access_id'].eql?(id) }
1119
+ Aspera.assert(!found.nil?, type: Error) { "Short link not found: #{id}" }
1139
1120
  shared_apifid.node_api.update("permissions/#{found['id']}", {access_levels: Api::AoC.expand_access_levels(access_levels)})
1140
1121
  when :delete
1141
1122
  found = shared_apifid.node_api.read('permissions', {file_id: shared_apifid.file_id, inherited: false, access_type: 'user', access_id: id}).first
1142
- Aspera.assert(!found.nil?, type: Error){"Short link not found: #{id}"}
1123
+ Aspera.assert(!found.nil?, type: Error) { "Short link not found: #{id}" }
1143
1124
  shared_apifid.node_api.delete("permissions/#{found['id']}")
1144
1125
  else Aspera.error_unexpected_value(op)
1145
1126
  end
@@ -1216,7 +1197,7 @@ module Aspera
1216
1197
  def sl_exec_delete(sl_shared_data_ws:, sl_short_list:, sl_link_type:, sl_perm_block:, short_link_id: nil, **)
1217
1198
  one_id = short_link_id
1218
1199
  if sl_link_type.eql?(:public)
1219
- found = sl_short_list[:items].find{ |item| item['id'].eql?(one_id)}
1200
+ found = sl_short_list[:items].find { |item| item['id'].eql?(one_id) }
1220
1201
  raise BadIdentifier.new('Short link', one_id) if found.nil?
1221
1202
  sl_perm_block&.call(:delete, found['resource_id'], nil)
1222
1203
  end
@@ -1232,14 +1213,14 @@ module Aspera
1232
1213
  # Shared implementation for short_link > show
1233
1214
  def sl_exec_show(sl_short_list:, short_link_id: nil, **)
1234
1215
  one_id = short_link_id
1235
- found = sl_short_list[:items].find{ |item| item['id'].eql?(one_id)}
1216
+ found = sl_short_list[:items].find { |item| item['id'].eql?(one_id) }
1236
1217
  raise BadIdentifier.new('Short link', one_id) if found.nil?
1237
1218
  Result::SingleObject.new(found, fields: Formatter.all_but('data'))
1238
1219
  end
1239
1220
 
1240
1221
  # Shared implementation for short_link > modify
1241
1222
  def sl_exec_modify(custom_data = {}, sl_shared_data:, sl_short_list:, sl_link_type:, sl_perm_block:, short_link_id: nil, **)
1242
- Aspera.assert_values(sl_link_type, [:public], type: Cli::BadArgument){'link_type'}
1223
+ Aspera.assert_values(sl_link_type, [:public], type: Cli::BadArgument) { 'link_type' }
1243
1224
  one_id = short_link_id
1244
1225
  node_file = sl_shared_data.slice(:node_id, :file_id)
1245
1226
  modify_payload = {edit_access: true, json_query: node_file}
@@ -1251,7 +1232,7 @@ module Aspera
1251
1232
  modify_payload[:password_enabled] = false
1252
1233
  end
1253
1234
  if custom_data.delete('access_levels')
1254
- found = sl_short_list[:items].find{ |item| item['id'].eql?(one_id)}
1235
+ found = sl_short_list[:items].find { |item| item['id'].eql?(one_id) }
1255
1236
  raise BadIdentifier.new('Short link', one_id) if found.nil?
1256
1237
  sl_perm_block&.call(:update, found['resource_id'], nil)
1257
1238
  end
@@ -1272,20 +1253,22 @@ module Aspera
1272
1253
  end
1273
1254
 
1274
1255
  # files > FILES_COMMANDS (all Gen4 node commands except :transfer, handled above)
1275
- FILES_COMMANDS.reject{ |a| a.eql?(:transfer)}.each do |action|
1276
- define_action_method([:files, action]) do
1277
- execute_nodegen4_command(action, aoc_api.home[:node_id], file_id: aoc_api.home[:file_id], scope: Api::Node::Scope::USER)
1256
+ FILES_COMMANDS.reject { |a| a.eql?(:transfer) }.each do |action|
1257
+ define_action_method([:files, action]) do |**ctx|
1258
+ execute_nodegen4_command(action, aoc_api.home[:node_id], file_id: aoc_api.home[:file_id], scope: Api::Node::Scope::USER, **ctx)
1278
1259
  end
1279
1260
  end
1280
1261
 
1281
1262
  # admin > application > instance > <type> > show|modify
1282
1263
  APP_TYPES.each do |app_type|
1283
- define_action_method([:admin, :application, :instance, app_type, :show]) do |res_id:, **|
1284
- Result::SingleObject.new(aoc_api.read("admin/apps_new/#{app_type}/#{res_id}", query_read_delete))
1264
+ define_action_method([:admin, :application, :instance, app_type, :show]) do |**kwargs|
1265
+ app_id = kwargs[:"#{app_type}_id"]
1266
+ Result::SingleObject.new(aoc_api.read("admin/apps_new/#{app_type}/#{app_id}", query_read_delete))
1285
1267
  end
1286
1268
 
1287
- define_action_method([:admin, :application, :instance, app_type, :modify]) do |properties:, res_id:, **|
1288
- aoc_api.update("admin/apps_new/#{app_type}/#{res_id}", properties)
1269
+ define_action_method([:admin, :application, :instance, app_type, :modify]) do |properties:, **kwargs|
1270
+ app_id = kwargs[:"#{app_type}_id"]
1271
+ aoc_api.update("admin/apps_new/#{app_type}/#{app_id}", properties)
1289
1272
  Result::Status.new('modified')
1290
1273
  end
1291
1274
  end
@@ -1293,18 +1276,18 @@ module Aspera
1293
1276
  def action_admin_application_membership_create(membership:, **)
1294
1277
  data = membership.dup
1295
1278
  app_type = data.delete('app_type')
1296
- Aspera.assert_type(app_type, String){'app_type'}
1297
- Aspera.assert_values(app_type.to_sym, APP_TYPES){'app_type'}
1279
+ Aspera.assert_type(app_type, String) { 'app_type' }
1280
+ Aspera.assert_values(app_type.to_sym, APP_TYPES) { 'app_type' }
1298
1281
  Result::SingleObject.new(aoc_api.create("apps/#{app_type}/app_memberships", data))
1299
1282
  end
1300
1283
 
1301
1284
  # admin > application > membership > show|delete
1302
- def action_admin_application_membership_show(res_id:, **)
1303
- Result::SingleObject.new(aoc_api.read("apps/app_memberships/#{res_id}", query_read_delete))
1285
+ def action_admin_application_membership_show(membership_id:, **)
1286
+ Result::SingleObject.new(aoc_api.read("apps/app_memberships/#{membership_id}", query_read_delete))
1304
1287
  end
1305
1288
 
1306
- def action_admin_application_membership_delete(res_id:, **)
1307
- aoc_api.delete("apps/app_memberships/#{res_id}")
1289
+ def action_admin_application_membership_delete(membership_id:, **)
1290
+ aoc_api.delete("apps/app_memberships/#{membership_id}")
1308
1291
  Result::Status.new('deleted')
1309
1292
  end
1310
1293
 
@@ -1337,14 +1320,12 @@ module Aspera
1337
1320
 
1338
1321
  # admin > analytics > application_events
1339
1322
  def action_admin_analytics_application_events
1340
- analytics_api = build_analytics_api
1341
- events = analytics_api.read("organizations/#{aoc_api.current_user_info['organization_id']}/application_events")['application_events']
1323
+ events = build_analytics_api.read("organizations/#{aoc_api.current_user_info['organization_id']}/application_events")['application_events']
1342
1324
  Result::ObjectList.new(events)
1343
1325
  end
1344
1326
 
1345
1327
  # admin > analytics > transfers
1346
1328
  def action_admin_analytics_transfers(event_resource_type:, event_resource_id:, **)
1347
- analytics_api = build_analytics_api
1348
1329
  event_resource_id ||=
1349
1330
  case event_resource_type
1350
1331
  when :organizations then aoc_api.current_user_info['organization_id']
@@ -1367,15 +1348,14 @@ module Aspera
1367
1348
  filter['start_time'] = start_date_time unless start_date_time.nil?
1368
1349
  filter['stop_time'] = stop_date_time
1369
1350
  end
1370
- events = analytics_api.read("#{event_resource_type}/#{event_resource_id}/transfers", filter)['transfers']
1351
+ events = build_analytics_api.read("#{event_resource_type}/#{event_resource_id}/transfers", filter)['transfers']
1371
1352
  start_date_persistency&.save
1372
- events.each{ |tr_event| context.mailer.send_email_template(values: {ev: tr_event})} if !options.get_option(:notify_to).nil?
1353
+ events.each { |tr_event| context.mailer.send_email_template(values: {ev: tr_event}) } if !options.get_option(:notify_to).nil?
1373
1354
  Result::ObjectList.new(events)
1374
1355
  end
1375
1356
 
1376
1357
  # admin > analytics > files
1377
1358
  def action_admin_analytics_files(event_resource_type:, event_resource_id:, event_uuid:, **)
1378
- analytics_api = build_analytics_api
1379
1359
  event_resource_id =
1380
1360
  case event_resource_type
1381
1361
  when :organizations then aoc_api.current_user_info['organization_id']
@@ -1385,13 +1365,13 @@ module Aspera
1385
1365
  end if event_resource_id.to_s.empty?
1386
1366
  filter = query_read_delete(default: {})
1387
1367
  filter['limit'] ||= 100
1388
- events = analytics_api.read("#{event_resource_type}/#{event_resource_id}/transfers/#{event_uuid}/files", filter)['files']
1368
+ events = build_analytics_api.read("#{event_resource_type}/#{event_resource_id}/transfers/#{event_uuid}/files", filter)['files']
1389
1369
  Result::ObjectList.new(events)
1390
1370
  end
1391
1371
 
1392
1372
  # Lookup methods for arguments:(:identifier) + lookup: on admin resources.
1393
1373
  # One method per non-singleton resource; each delegates to get_resource_id_from_args.
1394
- ADMIN_OBJECTS.reject{ |r| ADMIN_OBJECT_CONFIG.dig(r, :singleton)}.each do |res|
1374
+ ADMIN_OBJECTS.reject { |r| ADMIN_OBJECT_CONFIG.dig(r, :singleton) }.each do |res|
1395
1375
  define_method(:"lookup_aoc_#{res}_id") do |_field, value, **|
1396
1376
  aoc_api.lookup_with_q(aoc_res_path(res), value: value)['id']
1397
1377
  end
@@ -1406,8 +1386,9 @@ module Aspera
1406
1386
  end
1407
1387
 
1408
1388
  # admin > <res> > show
1409
- ADMIN_OBJECTS.reject{ |r| ADMIN_OBJECT_CONFIG.dig(r, :singleton)}.each do |res|
1410
- define_action_method([:admin, res, :show]) do |res_id:, **|
1389
+ ADMIN_OBJECTS.reject { |r| ADMIN_OBJECT_CONFIG.dig(r, :singleton) }.each do |res|
1390
+ define_action_method([:admin, res, :show]) do |**kwargs|
1391
+ res_id = kwargs[:"#{res}_id"]
1411
1392
  c = aoc_res_cfg(res)
1412
1393
  Result::SingleObject.new(aoc_api.read("#{c[:path]}/#{res_id}", query_read_delete), fields: Formatter.all_but('certificate'))
1413
1394
  end
@@ -1421,15 +1402,14 @@ module Aspera
1421
1402
  end
1422
1403
 
1423
1404
  # admin > <res> > create
1424
- ADMIN_OBJECTS.reject{ |r| ADMIN_OBJECT_CONFIG.dig(r, :singleton)}.each do |res|
1405
+ ADMIN_OBJECTS.reject { |r| ADMIN_OBJECT_CONFIG.dig(r, :singleton) }.each do |res|
1425
1406
  define_action_method([:admin, res, :create]) do |data:, **|
1426
1407
  c = aoc_res_cfg(res)
1427
1408
  path = c[:path]
1428
1409
  # Special case: client_registration_token has a different creation URL
1429
1410
  path = 'admin/client_registration/token' if path.eql?('admin/client_registration_tokens')
1430
1411
  workspace_id = aoc_api.workspace_info[:id] if c[:require_ws_id]
1431
- is_bulk = options.get_option(:bulk)
1432
- Result.bulk(data, is_bulk: is_bulk, command: :create, id_result: c[:id_result], bfail: options.get_option(:bfail)) do |params|
1412
+ bulk_result(data, command: :create, id_result: c[:id_result]) do |params|
1433
1413
  params['workspace_id'] = workspace_id if c[:require_ws_id] && workspace_id && !params.key?('workspace_id')
1434
1414
  aoc_api.create(path, params)
1435
1415
  end
@@ -1437,8 +1417,9 @@ module Aspera
1437
1417
  end
1438
1418
 
1439
1419
  # admin > <res> > modify
1440
- ADMIN_OBJECTS.reject{ |r| ADMIN_OBJECT_CONFIG.dig(r, :singleton) || ADMIN_OBJECT_CONFIG.dig(r, :ops)&.then{ |o| !o.include?(:modify)}}.each do |res|
1441
- define_action_method([:admin, res, :modify]) do |data:, res_id:, **|
1420
+ ADMIN_OBJECTS.reject { |r| ADMIN_OBJECT_CONFIG.dig(r, :singleton) || ADMIN_OBJECT_CONFIG.dig(r, :ops)&.then { |o| !o.include?(:modify) } }.each do |res|
1421
+ define_action_method([:admin, res, :modify]) do |data:, **kwargs|
1422
+ res_id = kwargs[:"#{res}_id"]
1442
1423
  c = aoc_res_cfg(res)
1443
1424
  aoc_api.update("#{c[:path]}/#{res_id}", data)
1444
1425
  Result::Status.new('modified')
@@ -1450,11 +1431,10 @@ module Aspera
1450
1431
  cfg = ADMIN_OBJECT_CONFIG.fetch(r, {})
1451
1432
  cfg[:singleton] || (cfg[:ops] && !cfg[:ops].include?(:delete))
1452
1433
  end.each do |res|
1453
- define_action_method([:admin, res, :delete]) do |res_id:, **|
1434
+ define_action_method([:admin, res, :delete]) do |**kwargs|
1435
+ res_id = kwargs[:"#{res}_id"]
1454
1436
  c = aoc_res_cfg(res)
1455
- is_bulk = options.get_option(:bulk)
1456
- items = res_id.is_a?(Array) ? res_id : [res_id]
1457
- Result.bulk(items, is_bulk: is_bulk, command: :delete, bfail: options.get_option(:bfail)) do |one_id|
1437
+ bulk_result(res_id, command: :delete) do |one_id|
1458
1438
  aoc_api.delete("#{c[:path]}/#{one_id}")
1459
1439
  {'id' => one_id}
1460
1440
  end
@@ -1469,33 +1449,49 @@ module Aspera
1469
1449
  end
1470
1450
 
1471
1451
  # admin > client > set_pub_key
1472
- def action_admin_client_set_pub_key(private_key_pem:, res_id:, **)
1452
+ def action_admin_client_set_pub_key(private_key_pem:, client_id:, **)
1473
1453
  c = aoc_res_cfg(:client)
1474
1454
  the_public_key = OpenSSL::PKey::RSA.new(private_key_pem).public_key.to_s
1475
- aoc_api.update("#{c[:path]}/#{res_id}", {jwt_grant_enabled: true, public_key: the_public_key})
1455
+ aoc_api.update("#{c[:path]}/#{client_id}", {jwt_grant_enabled: true, public_key: the_public_key})
1476
1456
  Result::Success.new
1477
1457
  end
1478
1458
 
1459
+ # admin > ats — build and return an Ats plugin instance wired to the AoC ATS API.
1460
+ # Used as delegate_instance: target so --help traverses the Ats registry.
1461
+ # @return [Ats] configured Ats plugin instance
1462
+ def build_ats_plugin
1463
+ ats_api = Rest.new(**aoc_api.params.deep_merge({
1464
+ base_url: "#{aoc_api.base_url}/admin/ats/pub/v1",
1465
+ auth: {params: {scope: Api::AoC::Scope::ADMIN_USER}}
1466
+ }))
1467
+ Ats.new(context: context, api: ats_api)
1468
+ end
1469
+
1479
1470
  # admin > node > do | bearer_token — setup reuses the generic instance setup
1480
1471
  # (setup_admin_node_instance is auto-generated above, providing res_id:)
1481
1472
 
1482
1473
  # admin > node > do > <FILES_COMMAND>
1483
1474
  FILES_COMMANDS.each do |cmd|
1484
- define_action_method([:admin, :node, :do, cmd]) do |res_id:, **|
1485
- execute_nodegen4_command(cmd, res_id, scope: Api::Node::Scope::ADMIN)
1475
+ define_action_method([:admin, :node, :do, cmd]) do |node_id:, **ctx|
1476
+ execute_nodegen4_command(cmd, node_id, scope: Api::Node::Scope::ADMIN, **ctx)
1486
1477
  end
1487
1478
  end
1488
1479
 
1489
1480
  # admin > node > bearer_token
1490
- def action_admin_node_bearer_token(scope:, res_id:, **)
1481
+ def action_admin_node_bearer_token(scope:, node_id:, **)
1491
1482
  scope ||= Api::Node::Scope::ADMIN
1492
- node_api = aoc_api.node_api_from(node_id: res_id, scope: scope)
1483
+ node_api = aoc_api.node_api_from(node_id: node_id, scope: scope)
1493
1484
  Result::Text.new(node_api.oauth.authorization)
1494
1485
  end
1495
1486
 
1487
+ # admin > node > update_status
1488
+ def action_admin_node_update_status(node_id:, **)
1489
+ Result::SingleObject.new(aoc_api.read("#{aoc_res_path(:node)}/#{node_id}/update_status"), fields: %w[status error_time error_message])
1490
+ end
1491
+
1496
1492
  # admin > workspace > dropbox — res_id: already in ctx via arguments:(:identifier)
1497
- def setup_admin_workspace_dropbox(res_id:, **)
1498
- {ws_res_id: res_id}
1493
+ def setup_admin_workspace_dropbox(workspace_id:, **)
1494
+ {ws_res_id: workspace_id}
1499
1495
  end
1500
1496
 
1501
1497
  # admin > workspace > dropbox > list
@@ -1505,11 +1501,11 @@ module Aspera
1505
1501
  end
1506
1502
 
1507
1503
  # admin > workspace > shared_folder — res_id: already in ctx via arguments:(:identifier)
1508
- def setup_admin_workspace_shared_folder(res_id:, **)
1509
- resource_instance_path = "#{aoc_res_path(:workspace)}/#{res_id}"
1510
- query = options.get_option(:query) || Api::AoC.workspace_access(res_id).merge({'admin' => true})
1504
+ def setup_admin_workspace_shared_folder(workspace_id:, **)
1505
+ resource_instance_path = "#{aoc_res_path(:workspace)}/#{workspace_id}"
1506
+ query = options.get_option(:query) || Api::AoC.workspace_access(workspace_id).merge({'admin' => true})
1511
1507
  shared_folders = aoc_api.read_with_paging("#{resource_instance_path}/permissions", query)[:items]
1512
- {ws_res_id: res_id, shared_folders: shared_folders}
1508
+ {ws_res_id: workspace_id, shared_folders: shared_folders}
1513
1509
  end
1514
1510
 
1515
1511
  # admin > workspace > shared_folder > list
@@ -1519,7 +1515,7 @@ module Aspera
1519
1515
 
1520
1516
  # admin > workspace > shared_folder > node|member — sf_id: already in ctx via arguments:(:identifier)
1521
1517
  def resolve_sf_item(shared_folders:, sf_id:, **)
1522
- sf_item = shared_folders.find{ |i| i['id'].eql?(sf_id)}
1518
+ sf_item = shared_folders.find { |i| i['id'].eql?(sf_id) }
1523
1519
  Aspera.assert(sf_item, 'shared folder not found')
1524
1520
  {sf_item: sf_item}
1525
1521
  end
@@ -1529,8 +1525,8 @@ module Aspera
1529
1525
 
1530
1526
  # admin > workspace > shared_folder > node > <FILES_COMMAND>
1531
1527
  FILES_COMMANDS.each do |cmd|
1532
- define_action_method([:admin, :workspace, :shared_folder, :node, cmd]) do |sf_item:, **|
1533
- execute_nodegen4_command(cmd, sf_item['node_id'], file_id: sf_item['file_id'], scope: Api::Node::Scope::ADMIN)
1528
+ define_action_method([:admin, :workspace, :shared_folder, :node, cmd]) do |sf_item:, **ctx|
1529
+ execute_nodegen4_command(cmd, sf_item['node_id'], file_id: sf_item['file_id'], scope: Api::Node::Scope::ADMIN, **ctx)
1534
1530
  end
1535
1531
  end
1536
1532
 
@@ -1562,26 +1558,19 @@ module Aspera
1562
1558
  # (setup_admin_user_instance is auto-generated, providing res_id:)
1563
1559
  %i[preferences notifications].each do |pref|
1564
1560
  pref_path = pref.eql?(:preferences) ? 'user_interaction_preferences' : 'notification_preferences'
1565
- define_action_method([:admin, :user, pref, :show]) do |res_id:, **|
1566
- Result::SingleObject.new(aoc_api.read("#{aoc_res_path(:user)}/#{res_id}/#{pref_path}"))
1561
+ define_action_method([:admin, :user, pref, :show]) do |user_id:, **|
1562
+ Result::SingleObject.new(aoc_api.read("#{aoc_res_path(:user)}/#{user_id}/#{pref_path}"))
1567
1563
  end
1568
- define_action_method([:admin, :user, pref, :modify]) do |properties:, res_id:, **|
1569
- aoc_api.update("#{aoc_res_path(:user)}/#{res_id}/#{pref_path}", properties)
1564
+ define_action_method([:admin, :user, pref, :modify]) do |properties:, user_id:, **|
1565
+ aoc_api.update("#{aoc_res_path(:user)}/#{user_id}/#{pref_path}", properties)
1570
1566
  Result::Status.new('modified')
1571
1567
  end
1572
1568
  end
1573
1569
 
1574
- # automation > workflows > CRUD operations
1575
- Operations::ALL.each do |op|
1576
- define_action_method([:automation, :workflows, op]) do
1577
- entity_execute(api: @automation_api, entity: 'workflows', command: op)
1578
- end
1579
- end
1580
-
1581
1570
  # automation > workflows > action > * (TODO: not fully implemented)
1582
1571
  %i[list create show].each do |cmd|
1583
1572
  define_action_method([:automation, :workflows, :action, cmd]) do |wf_id:, **|
1584
- Log.log.warn{"Not implemented: #{cmd}"}
1573
+ Log.log.warn { "Not implemented: #{cmd}" }
1585
1574
  step = @automation_api.create('steps', {'workflow_id' => wf_id})
1586
1575
  @automation_api.update("workflows/#{wf_id}", {'step_order' => [step['id']]})
1587
1576
  action = @automation_api.create('actions', {'step_id' => step['id'], 'type' => 'manual'})
@@ -1593,9 +1582,9 @@ module Aspera
1593
1582
  def action_gateway(parameters: {}, **)
1594
1583
  require 'aspera/faspex_gw'
1595
1584
  parameters = parameters.symbolize_keys
1596
- uri = URI.parse(parameters.delete(:url){WebServerSimple::DEFAULT_URL})
1585
+ uri = URI.parse(parameters.delete(:url) { WebServerSimple::DEFAULT_URL })
1597
1586
  server = WebServerSimple.new(uri, **parameters.slice(*WebServerSimple::PARAMS))
1598
- Aspera.assert(parameters.except(*WebServerSimple::PARAMS).empty?){"unexpected parameters: #{parameters.except(*WebServerSimple::PARAMS).keys}"}
1587
+ Aspera.assert(parameters.except(*WebServerSimple::PARAMS).empty?) { "unexpected parameters: #{parameters.except(*WebServerSimple::PARAMS).keys}" }
1599
1588
  server.mount(uri.path, Faspex4GWServlet, aoc_api, aoc_api.workspace_info[:id])
1600
1589
  server.start
1601
1590
  return Result::Status.new('Gateway terminated')