aspera-cli 4.24.1 → 4.25.0.pre

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 (99) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +1064 -745
  4. data/CONTRIBUTING.md +43 -100
  5. data/README.md +1281 -720
  6. data/bin/ascli +20 -1
  7. data/bin/asession +23 -27
  8. data/lib/aspera/agent/base.rb +10 -21
  9. data/lib/aspera/agent/connect.rb +2 -3
  10. data/lib/aspera/agent/desktop.rb +2 -2
  11. data/lib/aspera/agent/direct.rb +49 -32
  12. data/lib/aspera/agent/factory.rb +31 -0
  13. data/lib/aspera/api/aoc.rb +134 -76
  14. data/lib/aspera/api/cos_node.rb +3 -2
  15. data/lib/aspera/api/faspex.rb +213 -0
  16. data/lib/aspera/api/node.rb +107 -94
  17. data/lib/aspera/ascmd.rb +1 -2
  18. data/lib/aspera/ascp/installation.rb +73 -58
  19. data/lib/aspera/ascp/management.rb +119 -23
  20. data/lib/aspera/assert.rb +39 -11
  21. data/lib/aspera/cli/error.rb +4 -2
  22. data/lib/aspera/cli/extended_value.rb +91 -67
  23. data/lib/aspera/cli/formatter.rb +62 -27
  24. data/lib/aspera/cli/hints.rb +8 -0
  25. data/lib/aspera/cli/info.rb +4 -4
  26. data/lib/aspera/cli/main.rb +76 -84
  27. data/lib/aspera/cli/manager.rb +352 -248
  28. data/lib/aspera/cli/plugins/alee.rb +5 -4
  29. data/lib/aspera/cli/plugins/aoc.rb +175 -195
  30. data/lib/aspera/cli/plugins/ats.rb +4 -4
  31. data/lib/aspera/cli/plugins/base.rb +343 -0
  32. data/lib/aspera/cli/plugins/basic_auth.rb +45 -0
  33. data/lib/aspera/cli/plugins/config.rb +283 -269
  34. data/lib/aspera/cli/plugins/console.rb +27 -22
  35. data/lib/aspera/cli/plugins/cos.rb +3 -3
  36. data/lib/aspera/cli/plugins/factory.rb +78 -0
  37. data/lib/aspera/cli/plugins/faspex.rb +49 -46
  38. data/lib/aspera/cli/plugins/faspex5.rb +113 -225
  39. data/lib/aspera/cli/plugins/faspio.rb +19 -18
  40. data/lib/aspera/cli/plugins/httpgw.rb +14 -13
  41. data/lib/aspera/cli/plugins/node.rb +162 -149
  42. data/lib/aspera/cli/plugins/oauth.rb +48 -0
  43. data/lib/aspera/cli/plugins/orchestrator.rb +129 -45
  44. data/lib/aspera/cli/plugins/preview.rb +30 -50
  45. data/lib/aspera/cli/plugins/server.rb +21 -21
  46. data/lib/aspera/cli/plugins/shares.rb +45 -47
  47. data/lib/aspera/cli/sync_actions.rb +50 -39
  48. data/lib/aspera/cli/transfer_agent.rb +35 -49
  49. data/lib/aspera/cli/transfer_progress.rb +6 -6
  50. data/lib/aspera/cli/version.rb +3 -3
  51. data/lib/aspera/cli/wizard.rb +70 -55
  52. data/lib/aspera/colors.rb +6 -0
  53. data/lib/aspera/command_line_builder.rb +59 -61
  54. data/lib/aspera/command_line_converter.rb +2 -1
  55. data/lib/aspera/coverage.rb +2 -2
  56. data/lib/aspera/data_repository.rb +1 -1
  57. data/lib/aspera/environment.rb +51 -41
  58. data/lib/aspera/faspex_gw.rb +7 -5
  59. data/lib/aspera/faspex_postproc.rb +1 -1
  60. data/lib/aspera/keychain/factory.rb +1 -2
  61. data/lib/aspera/keychain/macos_security.rb +1 -1
  62. data/lib/aspera/log.rb +37 -9
  63. data/lib/aspera/markdown.rb +31 -0
  64. data/lib/aspera/nagios.rb +7 -6
  65. data/lib/aspera/oauth/base.rb +25 -28
  66. data/lib/aspera/oauth/factory.rb +9 -9
  67. data/lib/aspera/oauth/url_json.rb +2 -1
  68. data/lib/aspera/oauth/web.rb +2 -2
  69. data/lib/aspera/preview/file_types.rb +23 -37
  70. data/lib/aspera/products/connect.rb +7 -6
  71. data/lib/aspera/products/desktop.rb +1 -4
  72. data/lib/aspera/products/other.rb +9 -1
  73. data/lib/aspera/products/transferd.rb +0 -1
  74. data/lib/aspera/rest.rb +168 -113
  75. data/lib/aspera/rest_error_analyzer.rb +4 -4
  76. data/lib/aspera/ssh.rb +7 -4
  77. data/lib/aspera/ssl.rb +41 -0
  78. data/lib/aspera/sync/args.schema.yaml +46 -3
  79. data/lib/aspera/sync/conf.schema.yaml +307 -123
  80. data/lib/aspera/sync/database.rb +2 -1
  81. data/lib/aspera/sync/operations.rb +135 -79
  82. data/lib/aspera/temp_file_manager.rb +17 -5
  83. data/lib/aspera/transfer/error.rb +16 -7
  84. data/lib/aspera/transfer/parameters.rb +35 -22
  85. data/lib/aspera/transfer/resumer.rb +74 -0
  86. data/lib/aspera/transfer/spec.rb +5 -5
  87. data/lib/aspera/transfer/spec.schema.yaml +170 -59
  88. data/lib/aspera/transfer/spec_doc.rb +49 -43
  89. data/lib/aspera/uri_reader.rb +2 -2
  90. data/lib/aspera/web_auth.rb +6 -6
  91. data/lib/transferd_pb.rb +2 -2
  92. data.tar.gz.sig +0 -0
  93. metadata +26 -11
  94. metadata.gz.sig +0 -0
  95. data/lib/aspera/cli/basic_auth_plugin.rb +0 -43
  96. data/lib/aspera/cli/plugin.rb +0 -333
  97. data/lib/aspera/cli/plugin_factory.rb +0 -81
  98. data/lib/aspera/resumer.rb +0 -77
  99. data/lib/aspera/transfer/error_info.rb +0 -91
@@ -2,11 +2,12 @@
2
2
 
3
3
  require 'aspera/api/alee'
4
4
  require 'aspera/nagios'
5
+ require 'aspera/cli/plugins/basic_auth'
5
6
 
6
7
  module Aspera
7
8
  module Cli
8
9
  module Plugins
9
- class Alee < Cli::BasicAuthPlugin
10
+ class Alee < BasicAuth
10
11
  ACTIONS = %i[health entitlement].freeze
11
12
 
12
13
  def execute_action
@@ -16,13 +17,13 @@ module Aspera
16
17
  nagios = Nagios.new
17
18
  begin
18
19
  api = Api::Alee.new(nil, nil, version: 'ping')
19
- result = api.call(operation: 'GET')
20
- raise "unexpected response: #{result[:http].body}" unless result[:http].body.eql?('pong')
20
+ http = api.read(nil, ret: :resp)
21
+ raise "unexpected response: #{http.body}" unless http.body.eql?('pong')
21
22
  nagios.add_ok('api', 'answered ok')
22
23
  rescue StandardError => e
23
24
  nagios.add_critical('api', e.to_s)
24
25
  end
25
- return nagios.result
26
+ Main.result_object_list(nagios.status_list)
26
27
  when :entitlement
27
28
  entitlement_id = options.get_option(:username, mandatory: true)
28
29
  customer_id = options.get_option(:password, mandatory: true)
@@ -1,10 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'aspera/cli/plugins/oauth'
3
4
  require 'aspera/cli/plugins/node'
4
5
  require 'aspera/cli/plugins/ats'
5
- require 'aspera/cli/basic_auth_plugin'
6
6
  require 'aspera/cli/transfer_agent'
7
7
  require 'aspera/cli/special_values'
8
+ require 'aspera/cli/wizard'
8
9
  require 'aspera/agent/node'
9
10
  require 'aspera/transfer/spec'
10
11
  require 'aspera/api/aoc'
@@ -18,11 +19,9 @@ require 'date'
18
19
  module Aspera
19
20
  module Cli
20
21
  module Plugins
21
- class Aoc < Cli::BasicAuthPlugin
22
+ class Aoc < Oauth
22
23
  # default redirect for AoC web auth
23
24
  REDIRECT_LOCALHOST = 'http://localhost:12345'
24
- # OAuth methods supported
25
- STD_AUTH_TYPES = %i[web jwt].freeze
26
25
  # admin objects that can be manipulated
27
26
  ADMIN_OBJECTS = %i[
28
27
  self
@@ -57,7 +56,7 @@ module Aspera
57
56
  # options and parameters for Api::AoC.new
58
57
  OPTIONS_NEW = %i[url auth client_id client_secret scope redirect_uri private_key passphrase username password workspace].freeze
59
58
 
60
- private_constant :REDIRECT_LOCALHOST, :STD_AUTH_TYPES, :ADMIN_OBJECTS, :PACKAGE_RECEIVED_BASE_QUERY, :OPTIONS_NEW, :PACKAGE_LIST_DEFAULT_FIELDS
59
+ private_constant :REDIRECT_LOCALHOST, :ADMIN_OBJECTS, :PACKAGE_RECEIVED_BASE_QUERY, :OPTIONS_NEW, :PACKAGE_LIST_DEFAULT_FIELDS
61
60
  class << self
62
61
  def application_name
63
62
  'Aspera on Cloud'
@@ -70,9 +69,9 @@ module Aspera
70
69
  base_url = "#{base_url}.#{Api::AoC::SAAS_DOMAIN_PROD}" unless base_url.include?('.')
71
70
  # AoC is only https
72
71
  return unless base_url.start_with?('https://')
73
- res_http = Rest.new(base_url: base_url, redirect_max: 0).call(operation: 'GET', subpath: 'auth/ping', return_error: true)[:http]
74
- return if res_http['Location'].nil?
75
- redirect_uri = URI.parse(res_http['Location'])
72
+ location = Rest.new(base_url: base_url, redirect_max: 0).call(operation: 'GET', subpath: 'auth/ping', exception: false, ret: :resp)['Location']
73
+ return if location.nil?
74
+ redirect_uri = URI.parse(location)
76
75
  od = Api::AoC.split_org_domain(URI.parse(base_url))
77
76
  return unless redirect_uri.path.end_with?("oauth2/#{od[:organization]}/login")
78
77
  # either in standard domain, or product name in page
@@ -82,108 +81,6 @@ module Aspera
82
81
  }
83
82
  end
84
83
 
85
- # @param url [String] url to check
86
- # @return [Bool] true if private key is required for the url (i.e. no passcode)
87
- def private_key_required?(url)
88
- # pub link do not need private key
89
- return Api::AoC.link_info(url)[:token].nil?
90
- end
91
-
92
- # @param object [Plugin] An instance of this class
93
- # @param private_key_path [String] path to private key
94
- # @param pub_key_pem [String] PEM of public key
95
- # @return [Hash] :preset_value, :test_args
96
- def wizard(object:, private_key_path: nil, pub_key_pem: nil)
97
- # set vars to look like object
98
- options = object.options
99
- formatter = object.formatter
100
- instance_url = options.get_option(:url, mandatory: true)
101
- pub_link_info = Api::AoC.link_info(instance_url)
102
- if !pub_link_info[:token].nil?
103
- pub_api = Rest.new(base_url: "https://#{URI.parse(pub_link_info[:url]).host}/api/v1")
104
- pub_info = pub_api.read('env/url_token_check', {token: pub_link_info[:token]})
105
- preset_value = {
106
- link: instance_url
107
- }
108
- preset_value[:password] = options.get_option(:password, mandatory: true) if pub_info['password_protected']
109
- return {
110
- preset_value: preset_value,
111
- test_args: 'organization'
112
- }
113
- end
114
- options.declare(:use_generic_client, 'Wizard: AoC: use global or org specific jwt client id', values: :bool, default: Api::AoC.saas_url?(instance_url))
115
- options.parse_options!
116
- # make username mandatory for jwt, this triggers interactive input
117
- wiz_username = options.get_option(:username, mandatory: true)
118
- raise "Username shall be an email in AoC: #{wiz_username}" if !(wiz_username =~ /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i)
119
- # Set the pub key and jwt tag in the user's profile automatically
120
- auto_set_pub_key = false
121
- auto_set_jwt = false
122
- # use browser authentication to bootstrap
123
- use_browser_authentication = false
124
- if options.get_option(:use_generic_client)
125
- formatter.display_status('Using global client_id.')
126
- formatter.display_status('Please Login to your Aspera on Cloud instance.')
127
- formatter.display_status('Navigate to: 👤 → Account Settings → Profile → Public Key')
128
- formatter.display_status('Check or update the value to:'.red.blink)
129
- formatter.display_status(pub_key_pem, hide_secrets: false)
130
- if !options.get_option(:test_mode)
131
- formatter.display_status('Once updated or validated, press enter.')
132
- Environment.instance.open_uri(instance_url)
133
- $stdin.gets
134
- end
135
- else
136
- formatter.display_status('Using organization specific client_id.')
137
- if options.get_option(:client_id).nil? || options.get_option(:client_secret).nil?
138
- formatter.display_status('Please login to your Aspera on Cloud instance.'.red)
139
- formatter.display_status('Navigate to: 𓃑 → Admin → Integrations → API Clients')
140
- formatter.display_status('Check or create in integration:')
141
- formatter.display_status('- name: cli')
142
- formatter.display_status("- redirect uri: #{REDIRECT_LOCALHOST}")
143
- formatter.display_status('- origin: localhost')
144
- formatter.display_status('Use the generated client id and secret in the following prompts.'.red)
145
- end
146
- Environment.instance.open_uri("#{instance_url}/admin/integrations/api-clients")
147
- options.get_option(:client_id, mandatory: true)
148
- options.get_option(:client_secret, mandatory: true)
149
- # use_browser_authentication = true
150
- end
151
- if use_browser_authentication
152
- formatter.display_status('We will use web authentication to bootstrap.')
153
- auto_set_pub_key = true
154
- auto_set_jwt = true
155
- Aspera.error_not_implemented
156
- # aoc_api.oauth.grant_method = :web
157
- # aoc_api.oauth.scope = Api::AoC::SCOPE_FILES_ADMIN
158
- # aoc_api.oauth.specific_parameters[:redirect_uri] = REDIRECT_LOCALHOST
159
- end
160
- myself = object.aoc_api.read('self')
161
- if auto_set_pub_key
162
- Aspera.assert(myself['public_key'].empty?, type: Cli::Error){'Public key is already set in profile (use --override=yes)'} unless option_override
163
- formatter.display_status('Updating profile with the public key.')
164
- aoc_api.update("users/#{myself['id']}", {'public_key' => pub_key_pem})
165
- end
166
- if auto_set_jwt
167
- formatter.display_status('Enabling JWT for client')
168
- aoc_api.update("clients/#{options.get_option(:client_id)}", {'jwt_grant_enabled' => true, 'explicit_authorization_required' => false})
169
- end
170
- preset_result = {
171
- url: instance_url,
172
- username: myself['email'],
173
- auth: :jwt.to_s,
174
- private_key: "@file:#{private_key_path}"
175
- }
176
- # set only if non nil
177
- %i[client_id client_secret].each do |s|
178
- o = options.get_option(s)
179
- preset_result[s.to_s] = o unless o.nil?
180
- end
181
- return {
182
- preset_value: preset_result,
183
- test_args: 'user profile show'
184
- }
185
- end
186
-
187
84
  # @param base [String] Base folder path
188
85
  # @return [String] Folder path that does jot exist, with possible .<number> extension
189
86
  def next_available_folder(base, always: false)
@@ -217,44 +114,123 @@ module Aspera
217
114
  end
218
115
  end
219
116
 
117
+ # @param wizard [Wizard] The wizard object
118
+ # @param app_url [Wizard] The wizard object
119
+ # @return [Hash] :preset_value, :test_args
120
+ def wizard(wizard, app_url)
121
+ pub_link_info = Api::AoC.link_info(app_url)
122
+ # public link case
123
+ if pub_link_info.key?(:token)
124
+ pub_api = Rest.new(base_url: "https://#{URI.parse(pub_link_info[:url]).host}/api/v1")
125
+ pub_info = pub_api.read('env/url_token_check', {token: pub_link_info[:token]})
126
+ preset_value = {
127
+ link: app_url
128
+ }
129
+ preset_value[:password] = options.get_option(:password, mandatory: true) if pub_info['password_protected']
130
+ return {
131
+ preset_value: preset_value,
132
+ test_args: 'organization'
133
+ }
134
+ end
135
+ options.declare(:use_generic_client, 'Wizard: AoC: use global or org specific jwt client id', allowed: Allowed::TYPES_BOOLEAN, default: Api::AoC.saas_url?(app_url))
136
+ options.parse_options!
137
+ # make username mandatory for jwt, this triggers interactive input
138
+ wiz_username = options.get_option(:username, mandatory: true)
139
+ wizard.check_email(wiz_username)
140
+ # Set the pub key and jwt tag in the user's profile automatically
141
+ auto_set_pub_key = false
142
+ auto_set_jwt = false
143
+ # use browser authentication to bootstrap
144
+ use_browser_authentication = false
145
+ private_key_path = wizard.ask_private_key(
146
+ user: wiz_username,
147
+ url: app_url,
148
+ page: '👤 → Account Settings → Profile → Public Key'
149
+ )
150
+ client_id = options.get_option(:client_id)
151
+ client_secret = options.get_option(:client_secret)
152
+ if client_id.nil? || client_secret.nil?
153
+ if options.get_option(:use_generic_client)
154
+ client_id = client_secret = nil
155
+ formatter.display_status('Using global client_id.')
156
+ else
157
+ formatter.display_status('Using organization specific client_id.')
158
+ formatter.display_status('Please login to your Aspera on Cloud instance.'.red)
159
+ formatter.display_status('Navigate to: 𓃑 → Admin → Integrations → API Clients')
160
+ formatter.display_status('Check or create in integration:')
161
+ formatter.display_status('- name: cli')
162
+ formatter.display_status("- redirect uri: #{REDIRECT_LOCALHOST}")
163
+ formatter.display_status('- origin: localhost')
164
+ formatter.display_status('Use the generated client id and secret in the following prompts.'.red)
165
+ Environment.instance.open_uri("#{app_url}/admin/integrations/api-clients")
166
+ client_id = options.get_option(:client_id, mandatory: true)
167
+ client_secret = options.get_option(:client_secret, mandatory: true)
168
+ # use_browser_authentication = true
169
+ end
170
+ end
171
+ if use_browser_authentication
172
+ formatter.display_status('We will use web authentication to bootstrap.')
173
+ auto_set_pub_key = true
174
+ auto_set_jwt = true
175
+ Aspera.error_not_implemented
176
+ # aoc_api.oauth.grant_method = :web
177
+ # aoc_api.oauth.scope = Api::AoC::SCOPE_FILES_ADMIN
178
+ # aoc_api.oauth.specific_parameters[:redirect_uri] = REDIRECT_LOCALHOST
179
+ end
180
+ myself = aoc_api.read('self')
181
+ if auto_set_pub_key
182
+ Aspera.assert(myself['public_key'].empty?, type: Cli::Error){'Public key is already set in profile (use --override=yes)'} unless option_override
183
+ formatter.display_status('Updating profile with the public key.')
184
+ aoc_api.update("users/#{myself['id']}", {'public_key' => pub_key_pem})
185
+ end
186
+ if auto_set_jwt
187
+ formatter.display_status('Enabling JWT for client')
188
+ aoc_api.update("clients/#{options.get_option(:client_id)}", {'jwt_grant_enabled' => true, 'explicit_authorization_required' => false})
189
+ end
190
+ return {
191
+ preset_value: {
192
+ url: app_url,
193
+ username: myself['email'],
194
+ auth: :jwt.to_s,
195
+ private_key: "@file:#{private_key_path}",
196
+ client_id: client_id,
197
+ client_secret: client_secret
198
+ }.compact,
199
+ test_args: 'user profile show'
200
+ }
201
+ end
202
+
220
203
  def initialize(**_)
221
204
  super
222
205
  @cache_workspace_info = nil
223
206
  @cache_home_node_file = nil
224
207
  @cache_api_aoc = nil
225
- options.declare(:auth, 'OAuth type of authentication', values: STD_AUTH_TYPES, default: :jwt)
226
- options.declare(:client_id, 'OAuth API client identifier')
227
- options.declare(:client_secret, 'OAuth API client secret')
228
- options.declare(:scope, 'OAuth scope for AoC API calls')
229
- options.declare(:redirect_uri, 'OAuth API client redirect URI')
230
- options.declare(:private_key, 'OAuth JWT RSA private key PEM value (prefix file path with @file:)')
231
- options.declare(:passphrase, 'RSA private key passphrase', types: String)
232
- options.declare(:workspace, 'Name of workspace', types: [String, NilClass], default: Api::AoC::DEFAULT_WORKSPACE)
233
- options.declare(:new_user_option, 'New user creation option for unknown package recipients', types: Hash)
234
- options.declare(:validate_metadata, 'Validate shared inbox metadata', values: :bool, default: true)
235
- options.declare(:package_folder, 'Field of package to use as folder name, or @none:', types: [String, NilClass])
208
+ options.declare(:workspace, 'Name of workspace', allowed: [String, NilClass], default: Api::AoC::DEFAULT_WORKSPACE)
209
+ options.declare(:new_user_option, 'New user creation option for unknown package recipients', allowed: Hash)
210
+ options.declare(:validate_metadata, 'Validate shared inbox metadata', allowed: Allowed::TYPES_BOOLEAN, default: true)
211
+ options.declare(:package_folder, 'Field of package to use as folder name, or @none:', allowed: [String, NilClass])
236
212
  options.parse_options!
237
213
  # add node plugin options (for manual)
238
214
  Node.declare_options(options)
239
215
  end
240
216
 
241
217
  def api_from_options(aoc_base_path)
242
- create_values = OPTIONS_NEW.each_with_object({
243
- subpath: aoc_base_path,
244
- secret_finder: config
245
- }) do |i, m|
246
- m[i] = options.get_option(i) unless options.get_option(i).nil?
247
- end
248
- create_values[:scope] = Api::AoC::SCOPE_FILES_USER if create_values[:scope].nil?
249
218
  # create an API object with the same options, but with a different subpath
250
- return Api::AoC.new(**create_values)
251
- rescue ArgumentError => e
252
- if (m = e.message.match(/missing keyword: :(.*)$/))
253
- raise Cli::Error, "Missing option: #{m[1]}"
254
- end
255
- raise
219
+ return new_with_options(
220
+ Api::AoC,
221
+ base: {
222
+ subpath: aoc_base_path,
223
+ secret_finder: config
224
+ },
225
+ add: {
226
+ scope: Api::AoC::SCOPE_FILES_USER,
227
+ workspace: nil
228
+ }
229
+ )
256
230
  end
257
231
 
232
+ # AoC Rest object
233
+ # @return [Rest]
258
234
  def aoc_api
259
235
  if @cache_api_aoc.nil?
260
236
  @cache_api_aoc = api_from_options(Api::AoC::API_V1)
@@ -300,56 +276,11 @@ module Aspera
300
276
  return "#{resource_class_path}/#{get_resource_id_from_args(resource_class_path)}"
301
277
  end
302
278
 
303
- # Call block with same query using paging and response information
304
- # block must return a hash with :data and :http keys
305
- # @return [Hash] {items: , total: }
306
- def api_call_paging(base_query = {})
307
- Aspera.assert_type(base_query, Hash){'query'}
308
- Aspera.assert(block_given?)
309
- # set default large page if user does not specify own parameters. AoC Caps to 1000 anyway
310
- base_query['per_page'] = 1000 unless base_query.key?('per_page')
311
- max_items = base_query.delete(MAX_ITEMS)
312
- max_pages = base_query.delete(MAX_PAGES)
313
- item_list = []
314
- total_count = nil
315
- current_page = base_query['page']
316
- current_page = 1 if current_page.nil?
317
- page_count = 0
318
- loop do
319
- query = base_query.clone
320
- query['page'] = current_page
321
- result = yield(query)
322
- Aspera.assert(result[:data])
323
- Aspera.assert(result[:http])
324
- total_count = result[:http]['X-Total-Count']
325
- page_count += 1
326
- current_page += 1
327
- add_items = result[:data]
328
- break if add_items.empty?
329
- # append new items to full list
330
- item_list += add_items
331
- break if !max_items.nil? && item_list.count >= max_items
332
- break if !max_pages.nil? && page_count >= max_pages
333
- formatter.long_operation_running("#{item_list.count} / #{total_count}") unless total_count.eql?(item_list.count.to_s)
334
- end
335
- formatter.long_operation_terminated
336
- item_list = item_list[0..max_items - 1] if !max_items.nil? && item_list.count > max_items
337
- return {items: item_list, total: total_count}
338
- end
339
-
340
- # read using the query and paging
341
- # @return [Hash] {data: , total: }
342
- def api_read_all(resource_class_path, base_query = {})
343
- return api_call_paging(base_query) do |query|
344
- aoc_api.call(operation: 'GET', subpath: resource_class_path, headers: {'Accept' => Rest::MIME_JSON}, query: query)
345
- end
346
- end
347
-
348
279
  # List all entities, given additional, default and user's queries
349
280
  # @param resource_class_path path to query on API
350
281
  # @param fields fields to display
351
282
  # @param base_query a query applied always
352
- # @param default_query default query unless overriden by user
283
+ # @param default_query default query unless overridden by user
353
284
  # @param &block (Optional) calls block with user's or default query
354
285
  def result_list(resource_class_path, fields: nil, base_query: {}, default_query: {})
355
286
  Aspera.assert_type(base_query, Hash)
@@ -357,7 +288,7 @@ module Aspera
357
288
  query = query_read_delete(default: default_query)
358
289
  # caller may add specific modifications or checks to query
359
290
  yield(query) if block_given?
360
- result = api_read_all(resource_class_path, base_query.merge(query).compact)
291
+ result = aoc_api.read_with_paging(resource_class_path, base_query.merge(query).compact, formatter: formatter)
361
292
  return Main.result_object_list(result[:items], fields: fields, total: result[:total])
362
293
  end
363
294
 
@@ -382,7 +313,7 @@ module Aspera
382
313
  Aspera.assert_type(query, Hash){'query'}
383
314
  PACKAGE_RECEIVED_BASE_QUERY.each{ |k, v| query[k] = v unless query.key?(k)}
384
315
  resolve_dropbox_name_default_ws_id(query)
385
- return api_read_all('packages', query.compact)
316
+ return aoc_api.read_with_paging('packages', query.compact, formatter: formatter)
386
317
  end
387
318
 
388
319
  NODE4_EXT_COMMANDS = %i[transfer].concat(Node::COMMANDS_GEN4).freeze
@@ -443,6 +374,7 @@ module Aspera
443
374
  Aspera.error_unreachable_line
444
375
  end
445
376
 
377
+ # @param resource_type [Symbol] One of ADMIN_OBJECTS
446
378
  def execute_resource_action(resource_type)
447
379
  # get path on API, resource type is singular, but api is plural
448
380
  resource_class_path =
@@ -460,8 +392,9 @@ module Aspera
460
392
  global_operations = %i[create list]
461
393
  supported_operations = %i[show modify]
462
394
  supported_operations.push(:delete, *global_operations) unless singleton_object
463
- supported_operations.push(:do) if resource_type.eql?(:node)
395
+ supported_operations.push(:do, :bearer_token) if resource_type.eql?(:node)
464
396
  supported_operations.push(:set_pub_key) if resource_type.eql?(:client)
397
+ supported_operations.push(:shared_folder, :dropbox) if resource_type.eql?(:workspace)
465
398
  command = options.get_next_command(supported_operations)
466
399
  # require identifier for non global commands
467
400
  if !singleton_object && !global_operations.include?(command)
@@ -522,9 +455,61 @@ module Aspera
522
455
  return Main.result_success
523
456
  when :do
524
457
  command_repo = options.get_next_command(NODE4_EXT_COMMANDS)
525
- # init context
526
- aoc_api.context = :files
527
458
  return execute_nodegen4_command(command_repo, res_id)
459
+ when :bearer_token
460
+ node_api = aoc_api.node_api_from(
461
+ node_id: res_id,
462
+ scope: options.get_next_argument('scope')
463
+ )
464
+ return Main.result_text(node_api.oauth.authorization)
465
+ when :dropbox
466
+ command_shared = options.get_next_command(%i[list])
467
+ case command_shared
468
+ when :list
469
+ query = options.get_option(:query) || {}
470
+ res_data = aoc_api.read('dropboxes', query.merge({'workspace_id'=>res_id}))
471
+ return Main.result_object_list(res_data, fields: %w[id name description])
472
+ end
473
+ when :shared_folder
474
+ query = options.get_option(:query) || Api::AoC.workspace_access(res_id).merge({'admin' => true})
475
+ shared_folders = aoc_api.read_with_paging("#{resource_instance_path}/permissions", query)[:items]
476
+ # inside a workspace
477
+ command_shared = options.get_next_command(%i[list member])
478
+ case command_shared
479
+ when :list
480
+ return Main.result_object_list(shared_folders, fields: %w[id node_name node_id file_id file.path tags.aspera.files.workspace.share_as])
481
+ when :member
482
+ shared_folder_id = instance_identifier
483
+ shared_folder = shared_folders.find{ |i| i['id'].eql?(shared_folder_id)}
484
+ Aspera.assert(shared_folder)
485
+ command_shared_member = options.get_next_command(%i[list])
486
+ case command_shared_member
487
+ when :list
488
+ node_api = aoc_api.node_api_from(
489
+ node_id: shared_folder['node_id'],
490
+ workspace_id: res_id,
491
+ workspace_name: nil,
492
+ scope: Api::Node::SCOPE_USER
493
+ )
494
+ result = node_api.read(
495
+ 'permissions',
496
+ {'file_id' => shared_folder['file_id'], 'tag' => "aspera.files.workspace.id=#{res_id}"}
497
+ )
498
+ result.each do |item|
499
+ item['member'] = begin
500
+ if Api::AoC.workspace_access?(item)
501
+ {'name'=>'[Internal permission]'}
502
+ else
503
+ aoc_api.read("admin/#{item['access_type']}s/#{item['access_id']}") rescue {'name': 'not found'}
504
+ end
505
+ rescue => e
506
+ {'name'=>e.to_s}
507
+ end
508
+ end
509
+ # TODO : read users and group name and add, if query "include_members"
510
+ return Main.result_object_list(result, fields: %w[access_type access_id access_level last_updated_at member.name member.email member.system_group_type member.system_group])
511
+ end
512
+ end
528
513
  else Aspera.error_unexpected_value(command)
529
514
  end
530
515
  end
@@ -691,7 +676,6 @@ module Aspera
691
676
  filter = query_read_delete(default: {})
692
677
  filter['limit'] ||= 100
693
678
  if options.get_option(:once_only, mandatory: true)
694
- aoc_api.context = :files
695
679
  saved_date = []
696
680
  start_date_persistency = PersistencyActionOnce.new(
697
681
  manager: persistency,
@@ -736,7 +720,6 @@ module Aspera
736
720
  return Main.result_object_list(events)
737
721
  end
738
722
  when :usage_reports
739
- aoc_api.context = :files
740
723
  return result_list('usage_reports', base_query: workspace_id_hash)
741
724
  end
742
725
  end
@@ -809,7 +792,7 @@ module Aspera
809
792
  }
810
793
  return result_list('short_links', fields: Formatter.all_but('data'), base_query: list_params) if command.eql?(:list)
811
794
  one_id = instance_identifier
812
- found = api_read_all('short_links', list_params)[:items].find{ |item| item['id'].eql?(one_id)}
795
+ found = aoc_api.read_with_paging('short_links', list_params, formatter: formatter)[:items].find{ |item| item['id'].eql?(one_id)}
813
796
  raise Cli::BadIdentifier.new('Short link', one_id) if found.nil?
814
797
  return Main.result_single_object(found, fields: Formatter.all_but('data'))
815
798
  when :modify
@@ -876,7 +859,6 @@ module Aspera
876
859
  command = options.get_next_command(ACTIONS)
877
860
  if %i[files packages].include?(command)
878
861
  default_flag = ' (default)' if options.get_option(:workspace).eql?(:default)
879
- aoc_api.context = command
880
862
  formatter.display_status("Workspace: #{aoc_api.workspace[:name].to_s.red}#{default_flag}")
881
863
  if !aoc_api.private_link.nil?
882
864
  folder_name = aoc_api.node_api_from(node_id: aoc_api.home[:node_id]).read("files/#{aoc_api.home[:file_id]}")['name']
@@ -908,7 +890,6 @@ module Aspera
908
890
  when :list
909
891
  return result_list('workspaces', fields: %w[id name])
910
892
  when :current
911
- aoc_api.context = :files
912
893
  return Main.result_single_object(aoc_api.workspace)
913
894
  end
914
895
  when :profile
@@ -961,7 +942,7 @@ module Aspera
961
942
  # enforce workspace id from link (should be already ok, but in case user wanted to override)
962
943
  package_data['workspace_id'] = aoc_api.public_link['data']['workspace_id']
963
944
  end
964
- package_data['encryption_at_rest'] = true if transfer.option_transfer_spec['content_protection'].eql?('encrypt')
945
+ package_data['encryption_at_rest'] = true if transfer.user_transfer_spec['content_protection'].eql?('encrypt')
965
946
  # transfer may raise an error
966
947
  created_package = aoc_api.create_package_simple(package_data, option_validate, new_user_option)
967
948
  Main.result_transfer(transfer.start(created_package[:spec], rest_token: created_package[:node]))
@@ -1024,7 +1005,7 @@ module Aspera
1024
1005
  destination_folder,
1025
1006
  Environment.instance.sanitized_filename(package_info[per_package_field1])
1026
1007
  )
1027
- transfer.option_transfer_spec['destination_root'] = self.class.unique_folder(
1008
+ transfer.user_transfer_spec['destination_root'] = self.class.unique_folder(
1028
1009
  folder,
1029
1010
  extension: per_package_field2.eql?('seq') ? :seq : package_info[per_package_field2],
1030
1011
  always: per_package_sub_always
@@ -1119,9 +1100,9 @@ module Aspera
1119
1100
  when :instances
1120
1101
  return entity_execute(api: aoc_api, entity: 'workflow_instances')
1121
1102
  when :workflows
1122
- wf_command = options.get_next_command(%i[action launch].concat(Plugin::ALL_OPS))
1103
+ wf_command = options.get_next_command(%i[action launch].concat(ALL_OPS))
1123
1104
  case wf_command
1124
- when *Plugin::ALL_OPS
1105
+ when *ALL_OPS
1125
1106
  return entity_execute(
1126
1107
  api: automation_api,
1127
1108
  entity: 'workflows',
@@ -1152,7 +1133,6 @@ module Aspera
1152
1133
  uri = URI.parse(parameters.delete(:url){WebServerSimple::DEFAULT_URL})
1153
1134
  server = WebServerSimple.new(uri, **parameters.slice(*WebServerSimple::PARAMS))
1154
1135
  Aspera.assert(parameters.except(*WebServerSimple::PARAMS).empty?)
1155
- aoc_api.context = :files
1156
1136
  server.mount(uri.path, Faspex4GWServlet, aoc_api, aoc_api.workspace[:id])
1157
1137
  server.start
1158
1138
  return Main.result_status('Gateway terminated')
@@ -2,6 +2,7 @@
2
2
 
3
3
  # cspell:ignore trustpolicy
4
4
 
5
+ require 'aspera/cli/plugins/base'
5
6
  require 'aspera/cli/plugins/node'
6
7
  require 'aspera/api/ats'
7
8
  require 'aspera/api/aoc'
@@ -13,7 +14,7 @@ module Aspera
13
14
  module Plugins
14
15
  # Access Aspera Transfer Service
15
16
  # https://developer.ibm.com/aspera/docs/ats-api-reference/creating-ats-api-keys/
16
- class Ats < Cli::Plugin
17
+ class Ats < Base
17
18
  # columns for list of cloud providers
18
19
  CLOUD_TABLE = %w[id name].freeze
19
20
  private_constant :CLOUD_TABLE
@@ -25,7 +26,6 @@ module Aspera
25
26
  options.declare(:instance, 'ATS instance in ibm cloud')
26
27
  options.declare(:ats_key, 'ATS key identifier (ats_xxx)')
27
28
  options.declare(:ats_secret, 'ATS key secret')
28
- options.declare(:params, 'Parameters access key creation (@json:)')
29
29
  options.declare(:cloud, 'Cloud provider')
30
30
  options.declare(:region, 'Cloud region')
31
31
  options.parse_options!
@@ -59,7 +59,7 @@ module Aspera
59
59
  access_key_id = instance_identifier unless %i[create list].include?(command)
60
60
  case command
61
61
  when :create
62
- params = options.get_option(:params) || {}
62
+ params = value_create_modify(command: command, default: {})
63
63
  server_data = nil
64
64
  # if transfer_server_id not provided, get it from command line options
65
65
  if !params.key?('transfer_server_id')
@@ -92,7 +92,7 @@ module Aspera
92
92
  return Main.result_single_object(res)
93
93
  # TODO : action : modify, with "PUT"
94
94
  when :list
95
- params = options.get_option(:params) || {'offset' => 0, 'max_results' => 1000}
95
+ params = query_read_delete(default: {'offset' => 0, 'max_results' => 1000})
96
96
  res = ats_api_pub_v1.read('access_keys', params)
97
97
  return Main.result_object_list(res['data'], fields: ['name', 'id', 'created.at', 'modified.at'])
98
98
  when :show