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
@@ -8,6 +8,8 @@ require 'aspera/api/ats'
8
8
  require 'aspera/api/aoc'
9
9
  require 'aspera/api/alee'
10
10
  require 'aspera/assert'
11
+ require 'aspera/rainbow'
12
+ using Rainbow
11
13
 
12
14
  module Aspera
13
15
  module Cli
@@ -33,26 +35,26 @@ module Aspera
33
35
  super(**base_args)
34
36
  @ats_api_open = Api::Ats.new
35
37
  @ats_api_auth = api
36
- options.parse_options!
37
38
  end
38
39
 
39
40
  # --- DSL ---
40
41
 
41
- command :cluster, description: 'Display general ATS cluster information (public API, no auth)'
42
+ command :cluster, description: 'Show general ATS cluster information (public API, no auth)'
42
43
  command :access_key, description: 'Manage ATS access keys'
43
44
  command :api_key, description: 'Manage credential to access ATS API', condition: :api_key_available?
44
- command :aws_trust_policy, description: 'Show AWS trust policy', action: lambda { Result::SingleObject.new(ats_api.read('aws/trustpolicy', {region: options.get_option(:region, mandatory: true)})) }
45
+ command :aws_trust_policy, description: 'Show AWS trust policy', action: ->(**) { Result::SingleObject.new(ats_api.read('aws/trustpolicy', {region: options.get_option(:region, mandatory: true)})) }
45
46
 
46
- commands_under(:cluster) do
47
- command :clouds, description: 'List cloud providers', action: lambda { Result::ObjectList.new(@ats_api_open.cloud_names.map { |k, v| CLOUD_TABLE.zip([k, v]).to_h }) }
48
- command :list, description: 'List ATS servers', action: lambda { Result::ObjectList.new(@ats_api_open.all_servers, fields: %w[id cloud region]) }
49
- command :show, description: 'Show a specific server'
47
+ commands_under :cluster do
48
+ command :clouds, description: 'List cloud providers', action: ->(**) { Result::ObjectList.new(@ats_api_open.cloud_names.map { |k, v| CLOUD_TABLE.zip([k, v]).to_h }) }
49
+ command :list, description: 'List ATS servers', action: ->(**) { Result::ObjectList.new(@ats_api_open.all_servers, fields: %w[id cloud region]) }
50
+ command :show, description: 'Show a specific server (or use options cloud and region)',
51
+ arguments: [{name: :server_id, mandatory: false, default: nil}]
50
52
  end
51
53
 
52
- commands_under(:access_key) do
54
+ commands_under :access_key do
53
55
  command :create, description: 'Create an access key',
54
- arguments: [{name: :params, type: Hash, mandatory: false, default: {}}]
55
- command(:list, description: 'List access keys', action: lambda do
56
+ arguments: [{name: :access_key, type: Hash, mandatory: false, default: {}}]
57
+ command(:list, description: 'List access keys', action: lambda do |**|
56
58
  res = ats_api.read('access_keys', query_read_delete(default: {'offset' => 0, 'max_results' => 1000}))
57
59
  Result::ObjectList.new(res['data'], fields: ['name', 'id', 'created.at', 'modified.at'])
58
60
  end)
@@ -60,41 +62,52 @@ module Aspera
60
62
  arguments: [{name: :access_key_id, type: :identifier}],
61
63
  action: ->(access_key_id:, **) { Result::SingleObject.new(ats_api.read("access_keys/#{access_key_id}")) }
62
64
  command :modify, description: 'Modify an access key',
63
- arguments: [{name: :access_key_id, type: :identifier}, {name: :params, type: Hash}]
64
- command :delete, description: 'Delete an access key',
65
- arguments: [{name: :access_key_id, type: :identifier}]
65
+ arguments: [{name: :access_key_id, type: :identifier}, {name: :access_key, type: Hash}]
66
+ command(
67
+ :delete, description: 'Delete an access key',
68
+ arguments: [{name: :access_key_id, type: :identifier}],
69
+ action: lambda do |access_key_id:, **|
70
+ ats_api.delete("access_keys/#{access_key_id}")
71
+ Result::Status.new("deleted #{access_key_id}")
72
+ end
73
+ )
66
74
  command :node, description: 'Execute node commands via ATS access key',
67
75
  arguments: [{name: :access_key_id, type: :identifier}],
68
- setup: :setup_ak_node
76
+ mount: {plugin: Node, at: %i[access_keys do], instance: :ak_node_plugin}
69
77
  command :cluster, description: 'Show cluster info for an access key',
70
78
  arguments: [{name: :access_key_id, type: :identifier}]
71
- command :entitlement, description: 'Show ATS entitlement for an access key',
72
- arguments: [{name: :access_key_id, type: :identifier}]
73
- end
74
-
75
- commands_under %i[access_key node] do
76
- command :permission, description: 'Manage permissions'
77
- command :sync, description: 'Synchronize folders'
78
- Node::COMMANDS_GEN4_SPEC.each do |cmd, spec|
79
- command cmd, **spec
80
- end
79
+ command(
80
+ :entitlement, description: 'Show ATS entitlement for an access key',
81
+ arguments: [{name: :access_key_id, type: :identifier}],
82
+ action: lambda do |access_key_id:, **|
83
+ ak = ats_api.read("access_keys/#{access_key_id}")
84
+ api_bss = Api::Alee.new(ak['license']['entitlement_id'], ak['license']['customer_id'])
85
+ return Result::SingleObject.new(api_bss.read('entitlement'))
86
+ end
87
+ )
81
88
  end
82
89
 
83
- commands_under(:api_key) do
84
- command(:instances, description: 'List ATS instances in IBM Cloud', action: lambda do
90
+ commands_under :api_key do
91
+ command(:instances, description: 'List ATS instances in IBM Cloud', action: lambda do |**|
85
92
  instances = ats_api_v2_auth_ibm.read('instances')
86
93
  Log.log.warn { "more instances remaining: #{instances['remaining']}" } unless instances['remaining'].to_i.eql?(0)
87
94
  Result::ValueList.new(instances['data'], name: 'instance')
88
95
  end)
89
96
  command :create, description: 'Create an ATS API key',
90
- arguments: [{name: :params, type: Hash, mandatory: false, default: {}}],
91
- action: ->(params:, **) { Result::SingleObject.new(build_ats_ibm_api_with_instance.create('api_keys', params)) }
92
- command :list, description: 'List ATS API keys', action: lambda { Result::ValueList.new(build_ats_ibm_api_with_instance.read('api_keys', {'offset' => 0, 'max_results' => 1000})['data'], name: 'ats_id') }
97
+ arguments: [{name: :api_key, type: Hash, mandatory: false, default: {}}],
98
+ action: ->(api_key:, **) { Result::SingleObject.new(build_ats_ibm_api_with_instance.create('api_keys', api_key)) }
99
+ command :list, description: 'List ATS API keys', action: ->(**) { Result::ValueList.new(build_ats_ibm_api_with_instance.read('api_keys', {'offset' => 0, 'max_results' => 1000})['data'], name: 'ats_id') }
93
100
  command :show, description: 'Show an ATS API key',
94
101
  arguments: [{name: :api_key_id, type: :identifier}],
95
102
  action: ->(api_key_id:, **) { Result::SingleObject.new(build_ats_ibm_api_with_instance.read("api_keys/#{api_key_id}")) }
96
- command :delete, description: 'Delete an ATS API key',
97
- arguments: [{name: :api_key_id, type: :identifier}]
103
+ command(
104
+ :delete, description: 'Delete an ATS API key',
105
+ arguments: [{name: :api_key_id, type: :identifier}],
106
+ action: lambda do |api_key_id:, **|
107
+ build_ats_ibm_api_with_instance.delete("api_keys/#{api_key_id}")
108
+ Result::Status.new("deleted #{api_key_id}")
109
+ end
110
+ )
98
111
  end
99
112
 
100
113
  # --- conditions ---
@@ -116,7 +129,7 @@ module Aspera
116
129
  # require api key only if needed
117
130
  def ats_api
118
131
  return @ats_api_auth unless @ats_api_auth.nil?
119
- @ats_api_auth = Rest.new(
132
+ @ats_api_auth = Rest::Client.new(
120
133
  base_url: "#{Api::Ats::SERVICE_BASE_URL}/pub/v1",
121
134
  auth: {
122
135
  type: :basic,
@@ -127,7 +140,7 @@ module Aspera
127
140
  end
128
141
 
129
142
  def ats_api_v2_auth_ibm(rest_add_headers = {})
130
- return Rest.new(
143
+ return Rest::Client.new(
131
144
  base_url: "#{Api::Ats::SERVICE_BASE_URL}/v2",
132
145
  headers: rest_add_headers,
133
146
  auth: {
@@ -144,25 +157,26 @@ module Aspera
144
157
  )
145
158
  end
146
159
 
147
- def action_cluster_show
160
+ def action_cluster_show(server_id: nil, **)
148
161
  if options.get_option(:cloud) || options.get_option(:region)
149
162
  server_data = server_by_cloud_region
150
163
  else
151
- server_id = options.instance_identifier
164
+ Aspera.assert(server_id, type: Cli::MissingArgument) { 'server_id (or options cloud and region)' }
152
165
  server_data = @ats_api_open.all_servers.find { |i| i['id'].eql?(server_id) }
153
166
  raise BadIdentifier.new('server', server_id) if server_data.nil?
154
167
  end
155
168
  Result::SingleObject.new(server_data)
156
169
  end
157
170
 
158
- def action_access_key_create(params: {}, **)
171
+ def action_access_key_create(access_key: {}, **)
172
+ params = access_key
159
173
  server_data = nil
160
174
  # if transfer_server_id not provided, get it from command line options
161
175
  if !params.key?('transfer_server_id')
162
176
  server_data = server_by_cloud_region
163
177
  params['transfer_server_id'] = server_data['id']
164
178
  end
165
- Log.log.debug { "using params: #{params}".bg_red.gray }
179
+ Log.log.debug { "using params: #{params}".bg(:red).white }
166
180
  if params.key?('storage')
167
181
  case params['storage']['type']
168
182
  # here we need somehow to map storage type to field to get for auth end point
@@ -189,27 +203,17 @@ module Aspera
189
203
  # TODO : action : modify, with "PUT"
190
204
  end
191
205
 
192
- def action_access_key_modify(params:, access_key_id:, **)
206
+ def action_access_key_modify(access_key:, access_key_id:, **)
207
+ params = access_key
193
208
  params['id'] = access_key_id
194
209
  ats_api.update("access_keys/#{access_key_id}", params)
195
210
  return Result::Status.new('modified')
196
211
  end
197
212
 
198
- def action_access_key_delete(access_key_id:, **)
199
- ats_api.delete("access_keys/#{access_key_id}")
200
- Result::Status.new("deleted #{access_key_id}")
201
- end
202
-
203
- def action_access_key_entitlement(access_key_id:, **)
204
- ak = ats_api.read("access_keys/#{access_key_id}")
205
- api_bss = Api::Alee.new(ak['license']['entitlement_id'], ak['license']['customer_id'])
206
- return Result::SingleObject.new(api_bss.read('entitlement'))
207
- end
208
-
209
- # Build the Node plugin for an ATS access key.
213
+ # access_key > node - mount target: Node plugin for an ATS access key.
210
214
  # access_key_id: is already in ctx via arguments: on the :node command.
211
- # @return [Hash] context hash containing :ak_node_plugin and :ak_root_file_id
212
- def setup_ak_node(access_key_id:, **)
215
+ # @return [Array(Node, Hash)] Node plugin and seed ctx for `access_keys do`
216
+ def ak_node_plugin(access_key_id:, **)
213
217
  ak_data = ats_api.read("access_keys/#{access_key_id}")
214
218
  server_data = @ats_api_open.all_servers.find { |i| i['id'].start_with?(ak_data['transfer_server_id']) }
215
219
  Aspera.assert(!server_data.nil?, type: Cli::Error) { 'no such server found' }
@@ -222,21 +226,7 @@ module Aspera
222
226
  password: context.secret_finder.lookup(url: node_url, username: access_key_id)
223
227
  }
224
228
  )
225
- {
226
- ak_node_plugin: Node.new(context: context, api: api_node),
227
- ak_root_file_id: ak_data['root_file_id']
228
- }
229
- end
230
-
231
- # One handler per COMMANDS_GEN4 - delegates to the Node plugin's DSL registry.
232
- # Pre-resolved arguments (e.g. path:) are forwarded via **ctx to avoid double token consumption.
233
- Node::COMMANDS_GEN4.each do |cmd|
234
- define_action_method([:access_key, :node, cmd]) do |ak_node_plugin:, ak_root_file_id:, **ctx|
235
- # For permission: the handler consumes the path first then re-dispatches to sub-commands.
236
- # Calling dispatch_from_registry with skip_setup would bypass path consumption and fail.
237
- next ak_node_plugin.send(:"action_access_keys_do_#{cmd}", do_root_file_id: ak_root_file_id, **ctx) if cmd.eql?(:permission)
238
- ak_node_plugin.dispatch_from_registry([:access_keys, :do, cmd], {do_root_file_id: ak_root_file_id, **ctx}, skip_setup: true)
239
- end
229
+ [Node.new(context: context, api: api_node), {do_root_file_id: ak_data['root_file_id']}]
240
230
  end
241
231
 
242
232
  def action_api_key_instances
@@ -245,14 +235,9 @@ module Aspera
245
235
  Result::ValueList.new(instances['data'], name: 'instance')
246
236
  end
247
237
 
248
- def action_api_key_delete(api_key_id:, **)
249
- build_ats_ibm_api_with_instance.delete("api_keys/#{api_key_id}")
250
- Result::Status.new("deleted #{api_key_id}")
251
- end
252
-
253
238
  def action_access_key_cluster(access_key_id:, **)
254
239
  ats_url = ats_api.base_url
255
- api_ak_auth = Rest.new(
240
+ api_ak_auth = Rest::Client.new(
256
241
  base_url: ats_url,
257
242
  auth: {
258
243
  type: :basic,