aspera-cli 4.26.2 → 4.27.1

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 (159) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +310 -21
  4. data/CONTRIBUTING.md +99 -14
  5. data/TODO.md +50 -0
  6. data/bin/ascli +3 -3
  7. data/docs/README.md +10977 -0
  8. data/docs/test-mcp-with-ai.md +202 -0
  9. data/lib/aspera/agent/base.rb +29 -7
  10. data/lib/aspera/agent/connect.rb +58 -21
  11. data/lib/aspera/agent/desktop.rb +53 -18
  12. data/lib/aspera/agent/direct.rb +71 -40
  13. data/lib/aspera/agent/factory.rb +3 -3
  14. data/lib/aspera/agent/httpgw.rb +55 -14
  15. data/lib/aspera/agent/node.rb +34 -2
  16. data/lib/aspera/agent/transferd.rb +48 -12
  17. data/lib/aspera/api/alee.rb +1 -1
  18. data/lib/aspera/api/aoc.rb +42 -44
  19. data/lib/aspera/api/cos_node.rb +10 -9
  20. data/lib/aspera/api/faspex.rb +16 -15
  21. data/lib/aspera/api/httpgw.rb +23 -23
  22. data/lib/aspera/api/node.rb +51 -70
  23. data/lib/aspera/api/queries/bss_subscription_account.graphql +18 -0
  24. data/lib/aspera/api/queries/bss_subscription_usage.graphql +18 -0
  25. data/lib/aspera/ascmd.rb +32 -30
  26. data/lib/aspera/ascp/installation.rb +75 -69
  27. data/lib/aspera/ascp/management.rb +7 -8
  28. data/lib/aspera/assert.rb +12 -13
  29. data/lib/aspera/cli/ascp_actions.rb +155 -0
  30. data/lib/aspera/cli/async_transfer_store.rb +125 -0
  31. data/lib/aspera/cli/bootstrapper.rb +197 -0
  32. data/lib/aspera/cli/command_registry.rb +145 -0
  33. data/lib/aspera/cli/command_spec.rb +141 -0
  34. data/lib/aspera/cli/context.rb +19 -6
  35. data/lib/aspera/cli/error.rb +16 -0
  36. data/lib/aspera/cli/extended_value.rb +27 -26
  37. data/lib/aspera/cli/formatter.rb +95 -59
  38. data/lib/aspera/cli/gem_checker.rb +65 -0
  39. data/lib/aspera/cli/hints.rb +3 -3
  40. data/lib/aspera/cli/http.rb +74 -30
  41. data/lib/aspera/cli/info.rb +2 -0
  42. data/lib/aspera/cli/mailer.rb +97 -0
  43. data/lib/aspera/cli/mcp_tool.rb +225 -0
  44. data/lib/aspera/cli/option_declarator.rb +77 -0
  45. data/lib/aspera/cli/options.schema.yaml +634 -7
  46. data/lib/aspera/cli/parser.rb +1217 -0
  47. data/lib/aspera/cli/plugins/alee.rb +20 -22
  48. data/lib/aspera/cli/plugins/aoc.rb +1178 -881
  49. data/lib/aspera/cli/plugins/ats.rb +205 -162
  50. data/lib/aspera/cli/plugins/base.rb +703 -175
  51. data/lib/aspera/cli/plugins/basic_auth.rb +7 -9
  52. data/lib/aspera/cli/plugins/config.rb +434 -757
  53. data/lib/aspera/cli/plugins/console.rb +107 -65
  54. data/lib/aspera/cli/plugins/cos.rb +46 -33
  55. data/lib/aspera/cli/plugins/factory.rb +6 -6
  56. data/lib/aspera/cli/plugins/faspex5.rb +627 -388
  57. data/lib/aspera/cli/plugins/faspio.rb +54 -51
  58. data/lib/aspera/cli/plugins/httpgw.rb +18 -25
  59. data/lib/aspera/cli/plugins/mcp.rb +279 -0
  60. data/lib/aspera/cli/plugins/node.rb +988 -840
  61. data/lib/aspera/cli/plugins/oauth.rb +7 -10
  62. data/lib/aspera/cli/plugins/orchestrator.rb +112 -135
  63. data/lib/aspera/cli/plugins/preview.rb +195 -157
  64. data/lib/aspera/cli/plugins/server.rb +141 -91
  65. data/lib/aspera/cli/plugins/shares.rb +343 -110
  66. data/lib/aspera/cli/preset_actions.rb +159 -0
  67. data/lib/aspera/cli/preset_manager.rb +82 -39
  68. data/lib/aspera/cli/result.rb +78 -26
  69. data/lib/aspera/cli/runner.rb +323 -153
  70. data/lib/aspera/cli/secret_finder.rb +40 -0
  71. data/lib/aspera/cli/special_values.rb +1 -0
  72. data/lib/aspera/cli/sync_actions.rb +89 -64
  73. data/lib/aspera/cli/terminal_formatter.rb +3 -3
  74. data/lib/aspera/cli/transfer_actions.rb +90 -0
  75. data/lib/aspera/cli/transfer_agent.rb +129 -64
  76. data/lib/aspera/cli/transfer_progress.rb +9 -9
  77. data/lib/aspera/cli/vault_manager.rb +79 -0
  78. data/lib/aspera/cli/version.rb +1 -1
  79. data/lib/aspera/cli/wizard.rb +28 -30
  80. data/lib/aspera/colors.rb +3 -3
  81. data/lib/aspera/command_line_builder.rb +34 -34
  82. data/lib/aspera/command_line_converter.rb +1 -1
  83. data/lib/aspera/coverage.rb +1 -2
  84. data/lib/aspera/data_repository.rb +2 -1
  85. data/lib/aspera/dot_container.rb +12 -12
  86. data/lib/aspera/environment.rb +30 -23
  87. data/lib/aspera/exec_spec.rb +13 -0
  88. data/lib/aspera/faspex_gw.rb +5 -5
  89. data/lib/aspera/faspex_postproc.rb +16 -10
  90. data/lib/aspera/graphql.rb +37 -0
  91. data/lib/aspera/hash_ext.rb +8 -2
  92. data/lib/aspera/json_rpc/client.rb +62 -0
  93. data/lib/aspera/json_rpc/version.rb +7 -0
  94. data/lib/aspera/keychain/base.rb +10 -3
  95. data/lib/aspera/keychain/encrypted_hash.rb +92 -17
  96. data/lib/aspera/keychain/factory.rb +20 -8
  97. data/lib/aspera/keychain/hashicorp_vault.rb +1 -1
  98. data/lib/aspera/keychain/macos_security.rb +23 -25
  99. data/lib/aspera/keychain/one_password_api.rb +86 -0
  100. data/lib/aspera/keychain/one_password_base.rb +34 -0
  101. data/lib/aspera/keychain/one_password_cli.rb +98 -0
  102. data/lib/aspera/link_header.rb +82 -0
  103. data/lib/aspera/log.rb +29 -15
  104. data/lib/aspera/markdown.rb +90 -7
  105. data/lib/aspera/nagios.rb +8 -8
  106. data/lib/aspera/node_simulator.rb +35 -27
  107. data/lib/aspera/oauth/base.rb +9 -10
  108. data/lib/aspera/oauth/boot.rb +5 -5
  109. data/lib/aspera/oauth/factory.rb +12 -10
  110. data/lib/aspera/oauth/jwt.rb +9 -11
  111. data/lib/aspera/oauth/web.rb +6 -6
  112. data/lib/aspera/persistency_action_once.rb +12 -12
  113. data/lib/aspera/persistency_folder.rb +18 -11
  114. data/lib/aspera/preview/file_types.rb +5 -5
  115. data/lib/aspera/preview/generator.rb +52 -49
  116. data/lib/aspera/preview/options.rb +3 -2
  117. data/lib/aspera/preview/terminal.rb +5 -5
  118. data/lib/aspera/preview/utils.rb +66 -19
  119. data/lib/aspera/products/connect.rb +2 -2
  120. data/lib/aspera/products/desktop.rb +1 -1
  121. data/lib/aspera/products/other.rb +3 -3
  122. data/lib/aspera/products/transferd.rb +4 -4
  123. data/lib/aspera/proxy_auto_config.rb +15 -14
  124. data/lib/aspera/rest.rb +159 -151
  125. data/lib/aspera/rest_error_analyzer.rb +6 -6
  126. data/lib/aspera/rest_errors_aspera.rb +0 -10
  127. data/lib/aspera/rest_list.rb +11 -10
  128. data/lib/aspera/schema/IBM Aspera Node API-4.4.6.yaml +6232 -0
  129. data/lib/aspera/schema/IBM Aspera faspio Gateway API-1.0.0.yaml +249 -0
  130. data/lib/aspera/schema/IBM Aspera on Cloud API-0.2.6-enhanced.yaml +1853 -510
  131. data/lib/aspera/schema/IBM_Aspera_Shares.yaml +4653 -0
  132. data/lib/aspera/schema/documentation.rb +65 -28
  133. data/lib/aspera/schema/reader.rb +136 -13
  134. data/lib/aspera/schema/registry.rb +60 -8
  135. data/lib/aspera/secret_hider.rb +12 -7
  136. data/lib/aspera/ssh.rb +64 -31
  137. data/lib/aspera/ssl.rb +5 -5
  138. data/lib/aspera/sync/conf.schema.yaml +2 -2
  139. data/lib/aspera/sync/database.rb +2 -2
  140. data/lib/aspera/sync/operations.rb +22 -25
  141. data/lib/aspera/temp_file_manager.rb +25 -5
  142. data/lib/aspera/timer_limiter.rb +1 -1
  143. data/lib/aspera/transfer/faux_file.rb +24 -11
  144. data/lib/aspera/transfer/parameters.rb +36 -34
  145. data/lib/aspera/transfer/result.rb +74 -0
  146. data/lib/aspera/transfer/resumer.rb +10 -10
  147. data/lib/aspera/transfer/spec.rb +18 -1
  148. data/lib/aspera/transfer/spec.schema.yaml +12 -3
  149. data/lib/aspera/uri_reader.rb +52 -15
  150. data/lib/aspera/web_auth.rb +8 -7
  151. data/lib/aspera/web_server_simple.rb +15 -12
  152. data/lib/aspera/yaml.rb +5 -4
  153. data.tar.gz.sig +0 -0
  154. metadata +34 -6
  155. metadata.gz.sig +0 -0
  156. data/lib/aspera/cli/manager.rb +0 -766
  157. data/lib/aspera/cli/plugins/faspex.rb +0 -539
  158. data/lib/aspera/json_rpc.rb +0 -52
  159. data/lib/aspera/transfer/uri.rb +0 -56
@@ -15,23 +15,97 @@ module Aspera
15
15
  # Access Aspera Transfer Service
16
16
  # https://developer.ibm.com/aspera/docs/ats-api-reference/creating-ats-api-keys/
17
17
  class Ats < Base
18
+ application_name 'Aspera Transfer Service'
18
19
  # columns for list of cloud providers
19
20
  CLOUD_TABLE = %w[id name].freeze
20
21
  private_constant :CLOUD_TABLE
22
+
23
+ option :ibm_api_key, description: 'IBM API key, see https://cloud.ibm.com/iam/apikeys'
24
+ option :instance, description: 'ATS instance in ibm cloud'
25
+ option :ats_key, description: 'ATS key identifier (ats_xxx)'
26
+ option :ats_secret, description: 'ATS key secret'
27
+ option :cloud, description: 'Cloud provider'
28
+ option :region, description: 'Cloud region'
29
+
30
+ use_options Node
31
+
21
32
  def initialize(api: nil, **base_args)
22
33
  super(**base_args)
23
34
  @ats_api_open = Api::Ats.new
24
35
  @ats_api_auth = api
25
- options.declare(:ibm_api_key, 'IBM API key, see https://cloud.ibm.com/iam/apikeys')
26
- options.declare(:instance, 'ATS instance in ibm cloud')
27
- options.declare(:ats_key, 'ATS key identifier (ats_xxx)')
28
- options.declare(:ats_secret, 'ATS key secret')
29
- options.declare(:cloud, 'Cloud provider')
30
- options.declare(:region, 'Cloud region')
31
36
  options.parse_options!
32
- Node.declare_options(options)
33
37
  end
34
38
 
39
+ # --- DSL ---
40
+
41
+ command :cluster, description: 'Display general ATS cluster information (public API, no auth)'
42
+ command :access_key, description: 'Manage ATS access keys'
43
+ 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
+
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'
50
+ end
51
+
52
+ commands_under(:access_key) do
53
+ 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
+ res = ats_api.read('access_keys', query_read_delete(default: {'offset' => 0, 'max_results' => 1000}))
57
+ Result::ObjectList.new(res['data'], fields: ['name', 'id', 'created.at', 'modified.at'])
58
+ end)
59
+ command :show, description: 'Show an access key',
60
+ arguments: [{name: :access_key_id, type: :identifier}],
61
+ action: ->(access_key_id:, **) { Result::SingleObject.new(ats_api.read("access_keys/#{access_key_id}")) }
62
+ 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}]
66
+ command :node, description: 'Execute node commands via ATS access key',
67
+ arguments: [{name: :access_key_id, type: :identifier}],
68
+ setup: :setup_ak_node
69
+ command :cluster, description: 'Show cluster info for an access key',
70
+ 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
81
+ end
82
+
83
+ commands_under(:api_key) do
84
+ command(:instances, description: 'List ATS instances in IBM Cloud', action: lambda do
85
+ instances = ats_api_v2_auth_ibm.read('instances')
86
+ Log.log.warn { "more instances remaining: #{instances['remaining']}" } unless instances['remaining'].to_i.eql?(0)
87
+ Result::ValueList.new(instances['data'], name: 'instance')
88
+ end)
89
+ 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') }
93
+ command :show, description: 'Show an ATS API key',
94
+ arguments: [{name: :api_key_id, type: :identifier}],
95
+ 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}]
98
+ end
99
+
100
+ # --- conditions ---
101
+
102
+ # api_key sub-tree is only available when authenticated via ATS key (not injected API)
103
+ def api_key_available?
104
+ @ats_api_auth.nil?
105
+ end
106
+
107
+ # --- helpers ---
108
+
35
109
  def server_by_cloud_region
36
110
  # TODO: provide list ?
37
111
  cloud = options.get_option(:cloud, mandatory: true).upcase
@@ -52,113 +126,6 @@ module Aspera
52
126
  )
53
127
  end
54
128
 
55
- def execute_action_access_key
56
- commands = %i[create list show modify delete node cluster entitlement]
57
- command = options.get_next_command(commands)
58
- # those do not require access key id
59
- access_key_id = options.instance_identifier unless %i[create list].include?(command)
60
- case command
61
- when :create
62
- params = value_create_modify(command: command, default: {})
63
- server_data = nil
64
- # if transfer_server_id not provided, get it from command line options
65
- if !params.key?('transfer_server_id')
66
- server_data = server_by_cloud_region
67
- params['transfer_server_id'] = server_data['id']
68
- end
69
- Log.log.debug{"using params: #{params}".bg_red.gray}
70
- if params.key?('storage')
71
- case params['storage']['type']
72
- # here we need somehow to map storage type to field to get for auth end point
73
- when 'ibm-s3'
74
- server_data2 = nil
75
- if server_data.nil?
76
- server_data2 = @ats_api_open.all_servers.find{ |s| s['id'].eql?(params['transfer_server_id'])}
77
- raise "no such transfer server id: #{params['transfer_server_id']}" if server_data2.nil?
78
- else
79
- server_data2 = @ats_api_open.all_servers.find do |s|
80
- s['cloud'].eql?(server_data['cloud']) &&
81
- s['region'].eql?(server_data['region']) &&
82
- s.key?('s3_authentication_endpoint')
83
- end
84
- raise "no such transfer server id: #{params['transfer_server_id']}" if server_data2.nil?
85
- # specific one do not have s3 end point in id
86
- params['transfer_server_id'] = server_data2['id']
87
- end
88
- params['storage']['endpoint'] = server_data2['s3_authentication_endpoint'] if !params['storage'].key?('authentication_endpoint')
89
- end
90
- end
91
- res = ats_api.create('access_keys', params)
92
- return Result::SingleObject.new(res)
93
- # TODO : action : modify, with "PUT"
94
- when :list
95
- params = query_read_delete(default: {'offset' => 0, 'max_results' => 1000})
96
- res = ats_api.read('access_keys', params)
97
- return Result::ObjectList.new(res['data'], fields: ['name', 'id', 'created.at', 'modified.at'])
98
- when :show
99
- res = ats_api.read("access_keys/#{access_key_id}")
100
- return Result::SingleObject.new(res)
101
- when :modify
102
- params = value_create_modify(command: command)
103
- params['id'] = access_key_id
104
- ats_api.update("access_keys/#{access_key_id}", params)
105
- return Result::Status.new('modified')
106
- when :entitlement
107
- ak = ats_api.read("access_keys/#{access_key_id}")
108
- api_bss = Api::Alee.new(ak['license']['entitlement_id'], ak['license']['customer_id'])
109
- return Result::SingleObject.new(api_bss.read('entitlement'))
110
- when :delete
111
- ats_api.delete("access_keys/#{access_key_id}")
112
- return Result::Status.new("deleted #{access_key_id}")
113
- when :node
114
- ak_data = ats_api.read("access_keys/#{access_key_id}")
115
- server_data = @ats_api_open.all_servers.find{ |i| i['id'].start_with?(ak_data['transfer_server_id'])}
116
- raise Cli::Error, 'no such server found' if server_data.nil?
117
- node_url = server_data['transfer_setup_url']
118
- api_node = Api::Node.new(
119
- base_url: node_url,
120
- auth: {
121
- type: :basic,
122
- username: access_key_id,
123
- password: config.lookup_secret(url: node_url, username: access_key_id)
124
- }
125
- )
126
- command = options.get_next_command(Node::COMMANDS_GEN4)
127
- return Node.new(context: context, api: api_node).execute_command_gen4(command, ak_data['root_file_id'])
128
- when :cluster
129
- ats_url = ats_api.base_url
130
- api_ak_auth = Rest.new(
131
- base_url: ats_url,
132
- auth: {
133
- type: :basic,
134
- username: access_key_id,
135
- password: config.lookup_secret(url: ats_url, username: access_key_id)
136
- }
137
- )
138
- return Result::SingleObject.new(api_ak_auth.read('servers'))
139
- else Aspera.error_unexpected_value(command)
140
- end
141
- end
142
-
143
- def execute_action_cluster_open
144
- command = options.get_next_command(%i[clouds list show])
145
- case command
146
- when :clouds
147
- return Result::ObjectList.new(@ats_api_open.cloud_names.map{ |k, v| CLOUD_TABLE.zip([k, v]).to_h})
148
- when :list
149
- return Result::ObjectList.new(@ats_api_open.all_servers, fields: %w[id cloud region])
150
- when :show
151
- if options.get_option(:cloud) || options.get_option(:region)
152
- server_data = server_by_cloud_region
153
- else
154
- server_id = options.instance_identifier
155
- server_data = @ats_api_open.all_servers.find{ |i| i['id'].eql?(server_id)}
156
- raise BadIdentifier.new('server', server_id) if server_data.nil?
157
- end
158
- return Result::SingleObject.new(server_data)
159
- end
160
- end
161
-
162
129
  def ats_api_v2_auth_ibm(rest_add_headers = {})
163
130
  return Rest.new(
164
131
  base_url: "#{Api::Ats::SERVICE_BASE_URL}/v2",
@@ -177,60 +144,136 @@ module Aspera
177
144
  )
178
145
  end
179
146
 
180
- def execute_action_api_key
181
- command = options.get_next_command(%i[instances create list show delete])
182
- concerned_id = options.instance_identifier if %i[show delete].include?(command)
183
- rest_add_header = {}
184
- if !command.eql?(:instances)
185
- instance = options.get_option(:instance)
186
- if instance.nil?
187
- # Take the first Aspera on Cloud transfer service instance ID if not provided by user
188
- instance = ats_api_v2_auth_ibm.read('instances')['data'].first
189
- formatter.display_status("using first instance: #{instance}")
147
+ def action_cluster_show
148
+ if options.get_option(:cloud) || options.get_option(:region)
149
+ server_data = server_by_cloud_region
150
+ else
151
+ server_id = options.instance_identifier
152
+ server_data = @ats_api_open.all_servers.find { |i| i['id'].eql?(server_id) }
153
+ raise BadIdentifier.new('server', server_id) if server_data.nil?
154
+ end
155
+ Result::SingleObject.new(server_data)
156
+ end
157
+
158
+ def action_access_key_create(params: {}, **)
159
+ server_data = nil
160
+ # if transfer_server_id not provided, get it from command line options
161
+ if !params.key?('transfer_server_id')
162
+ server_data = server_by_cloud_region
163
+ params['transfer_server_id'] = server_data['id']
164
+ end
165
+ Log.log.debug { "using params: #{params}".bg_red.gray }
166
+ if params.key?('storage')
167
+ case params['storage']['type']
168
+ # here we need somehow to map storage type to field to get for auth end point
169
+ when 'ibm-s3'
170
+ server_data2 = nil
171
+ if server_data.nil?
172
+ server_data2 = @ats_api_open.all_servers.find { |s| s['id'].eql?(params['transfer_server_id']) }
173
+ raise "no such transfer server id: #{params['transfer_server_id']}" if server_data2.nil?
174
+ else
175
+ server_data2 = @ats_api_open.all_servers.find do |s|
176
+ s['cloud'].eql?(server_data['cloud']) &&
177
+ s['region'].eql?(server_data['region']) &&
178
+ s.key?('s3_authentication_endpoint')
179
+ end
180
+ raise "no such transfer server id: #{params['transfer_server_id']}" if server_data2.nil?
181
+ # specific one do not have s3 end point in id
182
+ params['transfer_server_id'] = server_data2['id']
183
+ end
184
+ params['storage']['endpoint'] = server_data2['s3_authentication_endpoint'] if !params['storage'].key?('authentication_endpoint')
190
185
  end
191
- rest_add_header = {'X-ATS-Service-Instance-Id' => instance}
192
186
  end
193
- ats_ibm_api = ats_api_v2_auth_ibm(rest_add_header)
194
- case command
195
- when :instances
196
- instances = ats_ibm_api.read('instances')
197
- Log.log.warn{"more instances remaining: #{instances['remaining']}"} unless instances['remaining'].to_i.eql?(0)
198
- return Result::ValueList.new(instances['data'], name: 'instance')
199
- when :create
200
- created_key = ats_ibm_api.create('api_keys', value_create_modify(command: command, default: {}))
201
- return Result::SingleObject.new(created_key)
202
- when :list # list known api keys in ATS (this require an api_key ...)
203
- res = ats_ibm_api.read('api_keys', {'offset' => 0, 'max_results' => 1000})
204
- return Result::ValueList.new(res['data'], name: 'ats_id')
205
- when :show # show one of api_key in ATS
206
- res = ats_ibm_api.read("api_keys/#{concerned_id}")
207
- return Result::SingleObject.new(res)
208
- when :delete
209
- ats_ibm_api.delete("api_keys/#{concerned_id}")
210
- return Result::Status.new("deleted #{concerned_id}")
211
- else Aspera.error_unexpected_value(command)
187
+ res = ats_api.create('access_keys', params)
188
+ return Result::SingleObject.new(res)
189
+ # TODO : action : modify, with "PUT"
190
+ end
191
+
192
+ def action_access_key_modify(params:, access_key_id:, **)
193
+ params['id'] = access_key_id
194
+ ats_api.update("access_keys/#{access_key_id}", params)
195
+ return Result::Status.new('modified')
196
+ end
197
+
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.
210
+ # 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:, **)
213
+ ak_data = ats_api.read("access_keys/#{access_key_id}")
214
+ server_data = @ats_api_open.all_servers.find { |i| i['id'].start_with?(ak_data['transfer_server_id']) }
215
+ Aspera.assert(!server_data.nil?, type: Cli::Error) { 'no such server found' }
216
+ node_url = server_data['transfer_setup_url']
217
+ api_node = Api::Node.new(
218
+ base_url: node_url,
219
+ auth: {
220
+ type: :basic,
221
+ username: access_key_id,
222
+ password: context.secret_finder.lookup(url: node_url, username: access_key_id)
223
+ }
224
+ )
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)
212
239
  end
213
240
  end
214
241
 
215
- ACTIONS = %i[cluster access_key api_key aws_trust_policy].freeze
216
-
217
- # called for legacy and AoC
218
- def execute_action
219
- actions = ACTIONS.dup
220
- actions.delete(:api_key) unless @ats_api_auth.nil?
221
- command = options.get_next_command(actions)
222
- case command
223
- when :cluster # display general ATS cluster information, this uses public API, no auth
224
- return execute_action_cluster_open
225
- when :access_key
226
- return execute_action_access_key
227
- when :api_key # manage credential to access ATS API
228
- return execute_action_api_key
229
- when :aws_trust_policy
230
- res = ats_api.read('aws/trustpolicy', {region: options.get_option(:region, mandatory: true)})
231
- return Result::SingleObject.new(res)
232
- else Aspera.error_unexpected_value(command)
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}")
251
+ end
252
+
253
+ def action_access_key_cluster(access_key_id:, **)
254
+ ats_url = ats_api.base_url
255
+ api_ak_auth = Rest.new(
256
+ base_url: ats_url,
257
+ auth: {
258
+ type: :basic,
259
+ username: access_key_id,
260
+ password: context.secret_finder.lookup(url: ats_url, username: access_key_id)
261
+ }
262
+ )
263
+ return Result::SingleObject.new(api_ak_auth.read('servers'))
264
+ end
265
+
266
+ private
267
+
268
+ # Build the IBM Cloud ATS v2 API with an instance header.
269
+ # Reads instance from options; falls back to first available instance.
270
+ def build_ats_ibm_api_with_instance
271
+ instance = options.get_option(:instance)
272
+ if instance.nil?
273
+ instance = ats_api_v2_auth_ibm.read('instances')['data'].first
274
+ formatter.display_status("using first instance: #{instance}")
233
275
  end
276
+ ats_api_v2_auth_ibm({'X-ATS-Service-Instance-Id' => instance})
234
277
  end
235
278
  end
236
279
  end