aspera-cli 4.27.2 → 4.27.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +133 -0
  4. data/CONTRIBUTING.md +5 -2
  5. data/bin/ascli +3 -1
  6. data/docs/README.md +1006 -766
  7. data/lib/aspera/agent/base.rb +7 -2
  8. data/lib/aspera/agent/connect.rb +6 -8
  9. data/lib/aspera/agent/desktop.rb +2 -6
  10. data/lib/aspera/agent/direct.rb +52 -22
  11. data/lib/aspera/agent/node.rb +9 -8
  12. data/lib/aspera/agent/transferd.rb +2 -2
  13. data/lib/aspera/api/alee.rb +1 -1
  14. data/lib/aspera/api/aoc.rb +14 -12
  15. data/lib/aspera/api/ats.rb +1 -1
  16. data/lib/aspera/api/cos_node.rb +2 -2
  17. data/lib/aspera/api/faspex.rb +11 -7
  18. data/lib/aspera/api/httpgw.rb +38 -35
  19. data/lib/aspera/api/node.rb +39 -34
  20. data/lib/aspera/ascmd.rb +3 -1
  21. data/lib/aspera/ascp/installation.rb +63 -28
  22. data/lib/aspera/ascp/management.rb +1 -0
  23. data/lib/aspera/assert.rb +4 -0
  24. data/lib/aspera/cli/ascp_actions.rb +20 -41
  25. data/lib/aspera/cli/async_transfer_store.rb +12 -11
  26. data/lib/aspera/cli/bootstrapper.rb +14 -16
  27. data/lib/aspera/cli/command_line.rb +252 -0
  28. data/lib/aspera/cli/command_registry.rb +215 -37
  29. data/lib/aspera/cli/command_spec.rb +104 -15
  30. data/lib/aspera/cli/completion/ascli.bash +12 -0
  31. data/lib/aspera/cli/completion/ascli.fish +16 -0
  32. data/lib/aspera/cli/completion/ascli.zsh +19 -0
  33. data/lib/aspera/cli/context.rb +3 -0
  34. data/lib/aspera/cli/deprecation.rb +37 -0
  35. data/lib/aspera/cli/extended_value.rb +6 -3
  36. data/lib/aspera/cli/formatter.rb +94 -80
  37. data/lib/aspera/cli/gem_checker.rb +1 -1
  38. data/lib/aspera/cli/hints.rb +7 -6
  39. data/lib/aspera/cli/http.rb +22 -34
  40. data/lib/aspera/cli/info.rb +3 -0
  41. data/lib/aspera/cli/mcp_tool.rb +47 -83
  42. data/lib/aspera/cli/option_declarator.rb +33 -42
  43. data/lib/aspera/cli/option_registry.rb +69 -0
  44. data/lib/aspera/cli/option_types.rb +105 -0
  45. data/lib/aspera/cli/option_value.rb +278 -0
  46. data/lib/aspera/cli/options.schema.yaml +124 -15
  47. data/lib/aspera/cli/parser.rb +333 -862
  48. data/lib/aspera/cli/plugins/alee.rb +7 -4
  49. data/lib/aspera/cli/plugins/aoc.rb +545 -518
  50. data/lib/aspera/cli/plugins/ats.rb +59 -80
  51. data/lib/aspera/cli/plugins/base.rb +221 -265
  52. data/lib/aspera/cli/plugins/basic_auth.rb +2 -10
  53. data/lib/aspera/cli/plugins/config.rb +263 -184
  54. data/lib/aspera/cli/plugins/console.rb +103 -39
  55. data/lib/aspera/cli/plugins/cos.rb +6 -23
  56. data/lib/aspera/cli/plugins/factory.rb +3 -0
  57. data/lib/aspera/cli/plugins/faspex5.rb +204 -182
  58. data/lib/aspera/cli/plugins/faspio.rb +6 -11
  59. data/lib/aspera/cli/plugins/httpgw.rb +8 -11
  60. data/lib/aspera/cli/plugins/mcp.rb +20 -55
  61. data/lib/aspera/cli/plugins/node.rb +300 -327
  62. data/lib/aspera/cli/plugins/orchestrator.rb +152 -110
  63. data/lib/aspera/cli/plugins/preview.rb +96 -105
  64. data/lib/aspera/cli/plugins/server.rb +78 -53
  65. data/lib/aspera/cli/plugins/shares.rb +80 -131
  66. data/lib/aspera/cli/preset_actions.rb +44 -27
  67. data/lib/aspera/cli/preset_manager.rb +44 -19
  68. data/lib/aspera/cli/prompt.rb +36 -0
  69. data/lib/aspera/cli/result.rb +42 -36
  70. data/lib/aspera/cli/runner.rb +32 -59
  71. data/lib/aspera/cli/special_values.rb +5 -0
  72. data/lib/aspera/cli/sync_actions.rb +51 -46
  73. data/lib/aspera/cli/terminal_formatter.rb +9 -3
  74. data/lib/aspera/cli/transfer_actions.rb +14 -9
  75. data/lib/aspera/cli/transfer_agent.rb +34 -38
  76. data/lib/aspera/cli/transfer_progress.rb +290 -55
  77. data/lib/aspera/cli/vault_manager.rb +0 -17
  78. data/lib/aspera/cli/version.rb +1 -1
  79. data/lib/aspera/cli/wizard.rb +5 -3
  80. data/lib/aspera/coverage.rb +1 -1
  81. data/lib/aspera/environment.rb +35 -5
  82. data/lib/aspera/faspex_gw.rb +2 -1
  83. data/lib/aspera/faspex_postproc.rb +1 -0
  84. data/lib/aspera/graphql.rb +5 -5
  85. data/lib/aspera/json_rpc/client.rb +5 -5
  86. data/lib/aspera/keychain/encrypted_hash.rb +2 -2
  87. data/lib/aspera/keychain/factory.rb +2 -1
  88. data/lib/aspera/keychain/one_password_api.rb +1 -1
  89. data/lib/aspera/link_header.rb +2 -2
  90. data/lib/aspera/log.rb +47 -27
  91. data/lib/aspera/markdown.rb +2 -0
  92. data/lib/aspera/mime.rb +25 -0
  93. data/lib/aspera/node_emulator.rb +759 -0
  94. data/lib/aspera/oauth/base.rb +37 -26
  95. data/lib/aspera/oauth/factory.rb +7 -3
  96. data/lib/aspera/oauth/generic.rb +1 -1
  97. data/lib/aspera/oauth/json_credentials.rb +34 -0
  98. data/lib/aspera/oauth/jwt.rb +4 -5
  99. data/lib/aspera/oauth/web.rb +9 -8
  100. data/lib/aspera/oauth.rb +1 -0
  101. data/lib/aspera/persistency_folder.rb +1 -3
  102. data/lib/aspera/preview/file_types.rb +4 -4
  103. data/lib/aspera/preview/generator.rb +11 -1
  104. data/lib/aspera/preview/options.schema.yaml +119 -0
  105. data/lib/aspera/preview/terminal.rb +4 -3
  106. data/lib/aspera/preview/utils.rb +9 -6
  107. data/lib/aspera/products/connect.rb +1 -1
  108. data/lib/aspera/rainbow.rb +7 -0
  109. data/lib/aspera/rest/aspera_errors.rb +72 -0
  110. data/lib/aspera/rest/call_error.rb +27 -0
  111. data/lib/aspera/rest/client.rb +523 -0
  112. data/lib/aspera/rest/error_analyzer.rb +113 -0
  113. data/lib/aspera/rest/list.rb +149 -0
  114. data/lib/aspera/rest/parameters.rb +55 -0
  115. data/lib/aspera/rest/util.rb +176 -0
  116. data/lib/aspera/rest.rb +7 -621
  117. data/lib/aspera/schema/IBM Aspera Console-enhanced.yaml +1125 -0
  118. data/lib/aspera/schema/IBM Aspera Faspex API-5.0-enhanced.yaml +0 -20
  119. data/lib/aspera/schema/IBM Aspera Orchestrator API-v1.yaml +1784 -0
  120. data/lib/aspera/schema/IBM Aspera on Cloud API-0.2.6-enhanced.yaml +1230 -137
  121. data/lib/aspera/schema/IBM Aspera on Cloud Automation API-1.0.5-enhanced.yaml +2395 -0
  122. data/lib/aspera/schema/IBM_Aspera_Shares.yaml +14 -13
  123. data/lib/aspera/schema/documentation.rb +13 -3
  124. data/lib/aspera/schema/reader.rb +12 -18
  125. data/lib/aspera/schema/registry.rb +23 -1
  126. data/lib/aspera/schema/validator.rb +92 -0
  127. data/lib/aspera/secret_hider.rb +36 -25
  128. data/lib/aspera/string_ext.rb +15 -0
  129. data/lib/aspera/temp_file_manager.rb +6 -5
  130. data/lib/aspera/transfer/parameters.rb +2 -0
  131. data/lib/aspera/transfer/spec.rb +1 -0
  132. data/lib/aspera/uri_reader.rb +11 -11
  133. data/lib/aspera/web_auth/index.html +147 -0
  134. data/lib/aspera/web_auth/server.rb +81 -0
  135. data.tar.gz.sig +0 -0
  136. metadata +43 -9
  137. metadata.gz.sig +0 -0
  138. data/lib/aspera/colors.rb +0 -79
  139. data/lib/aspera/node_simulator.rb +0 -344
  140. data/lib/aspera/preview/options.rb +0 -45
  141. data/lib/aspera/rest_call_error.rb +0 -25
  142. data/lib/aspera/rest_error_analyzer.rb +0 -111
  143. data/lib/aspera/rest_errors_aspera.rb +0 -58
  144. data/lib/aspera/rest_list.rb +0 -136
  145. 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,37 +226,12 @@ 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
240
- end
241
-
242
- def action_api_key_instances
243
- instances = ats_api_v2_auth_ibm.read('instances')
244
- Log.log.warn { "more instances remaining: #{instances['remaining']}" } unless instances['remaining'].to_i.eql?(0)
245
- Result::ValueList.new(instances['data'], name: 'instance')
246
- end
247
-
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}")
229
+ [Node.new(context: context, api: api_node), {do_root_file_id: ak_data['root_file_id']}]
251
230
  end
252
231
 
253
232
  def action_access_key_cluster(access_key_id:, **)
254
233
  ats_url = ats_api.base_url
255
- api_ak_auth = Rest.new(
234
+ api_ak_auth = Rest::Client.new(
256
235
  base_url: ats_url,
257
236
  auth: {
258
237
  type: :basic,
@@ -270,7 +249,7 @@ module Aspera
270
249
  def build_ats_ibm_api_with_instance
271
250
  instance = options.get_option(:instance)
272
251
  if instance.nil?
273
- instance = ats_api_v2_auth_ibm.read('instances')['data'].first
252
+ instance = ats_api_v2_auth_ibm.read('instances')['data']&.first
274
253
  formatter.display_status("using first instance: #{instance}")
275
254
  end
276
255
  ats_api_v2_auth_ibm({'X-ATS-Service-Instance-Id' => instance})