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,9 +2,11 @@
2
2
 
3
3
  # spellchecker: ignore workgroups mypackages passcode
4
4
 
5
- require 'aspera/cli/basic_auth_plugin'
5
+ require 'aspera/cli/plugins/oauth'
6
6
  require 'aspera/cli/extended_value'
7
7
  require 'aspera/cli/special_values'
8
+ require 'aspera/cli/wizard'
9
+ require 'aspera/api/faspex'
8
10
  require 'aspera/persistency_action_once'
9
11
  require 'aspera/id_generator'
10
12
  require 'aspera/nagios'
@@ -15,60 +17,7 @@ require 'securerandom'
15
17
  module Aspera
16
18
  module Cli
17
19
  module Plugins
18
- class Faspex5 < Cli::BasicAuthPlugin
19
- RECIPIENT_TYPES = %w[user workgroup external_user distribution_list shared_inbox].freeze
20
- PACKAGE_TERMINATED = %w[completed failed].freeze
21
- # list of supported mailbox types (to list packages)
22
- API_LIST_MAILBOX_TYPES = %w[inbox inbox_history inbox_all inbox_all_history outbox outbox_history pending pending_history all].freeze
23
- PACKAGE_SEND_FROM_REMOTE_SOURCE = 'remote_source'
24
- # Faspex API v5: get transfer spec for connect
25
- TRANSFER_CONNECT = 'connect'
26
- ADMIN_RESOURCES = %i[
27
- accounts distribution_lists contacts jobs workgroups shared_inboxes nodes oauth_clients registrations saml_configs
28
- metadata_profiles email_notifications alternate_addresses webhooks
29
- ].freeze
30
- # states for jobs not in final state
31
- JOB_RUNNING = %w[queued working].freeze
32
- PATH_STANDARD_ROOT = '/aspera/faspex'
33
- PATH_API_V5 = 'api/v5'
34
- # endpoint for authentication API
35
- PATH_AUTH = 'auth'
36
- PATH_HEALTH = 'configuration/ping'
37
- PATH_API_DETECT = "#{PATH_API_V5}/#{PATH_HEALTH}"
38
- # OAuth methods supported
39
- STD_AUTH_TYPES = %i[web jwt boot].freeze
40
- HEADER_ITERATION_TOKEN = 'X-Aspera-Next-Iteration-Token'
41
- HEADER_FASPEX_VERSION = 'X-IBM-Aspera'
42
- EMAIL_NOTIF_LIST = %w[
43
- welcome_email
44
- forgot_password
45
- package_received
46
- package_received_cc
47
- package_sent_cc
48
- package_downloaded
49
- package_downloaded_cc
50
- workgroup_package
51
- upload_result
52
- upload_result_cc
53
- relay_started_cc
54
- relay_finished_cc
55
- relay_error_cc
56
- shared_inbox_invitation
57
- shared_inbox_submit
58
- personal_invitation
59
- personal_submit
60
- account_approved
61
- account_denied
62
- package_file_processing_failed_sender
63
- package_file_processing_failed_recipient
64
- relay_failed_admin
65
- relay_failed
66
- admin_sync_failed
67
- sync_failed
68
- account_exist
69
- mfa_code
70
- ]
71
- private_constant :JOB_RUNNING, :RECIPIENT_TYPES, :PACKAGE_TERMINATED, :PATH_HEALTH, :API_LIST_MAILBOX_TYPES, :PACKAGE_SEND_FROM_REMOTE_SOURCE, :STD_AUTH_TYPES, :HEADER_ITERATION_TOKEN, :HEADER_FASPEX_VERSION, :EMAIL_NOTIF_LIST
20
+ class Faspex5 < Oauth
72
21
  class << self
73
22
  def application_name
74
23
  'Faspex'
@@ -78,19 +27,19 @@ module Aspera
78
27
  # add scheme if missing
79
28
  address_or_url = "https://#{address_or_url}" unless address_or_url.match?(%r{^[a-z]{1,6}://})
80
29
  urls = [address_or_url]
81
- urls.push("#{address_or_url}#{PATH_STANDARD_ROOT}") unless address_or_url.end_with?(PATH_STANDARD_ROOT)
30
+ urls.push("#{address_or_url}#{Api::Faspex::PATH_STANDARD_ROOT}") unless address_or_url.end_with?(Api::Faspex::PATH_STANDARD_ROOT)
82
31
  error = nil
83
32
  urls.each do |base_url|
84
33
  # Faspex is always HTTPS
85
34
  next unless base_url.start_with?('https://')
86
35
  api = Rest.new(base_url: base_url, redirect_max: 1)
87
- response = api.call(operation: 'GET', subpath: PATH_API_DETECT)[:http]
36
+ response = api.read(Api::Faspex::PATH_API_DETECT, ret: :resp)
88
37
  next unless response.code.start_with?('2') && response.body.strip.empty?
89
38
  # end is at -1, and subtract 1 for "/"
90
- url_length = -2 - PATH_API_DETECT.length
39
+ url_length = -2 - Api::Faspex::PATH_API_DETECT.length
91
40
  # take redirect if any
92
41
  return {
93
- version: response[HEADER_FASPEX_VERSION] || '5',
42
+ version: response[Api::Faspex::HEADER_FASPEX_VERSION] || '5',
94
43
  url: response.uri.to_s[0..url_length]
95
44
  }
96
45
  rescue StandardError => e
@@ -100,122 +49,57 @@ module Aspera
100
49
  raise error if error
101
50
  return
102
51
  end
52
+ end
103
53
 
104
- # @param object [Plugin] An instance of this class
105
- # @param private_key_path [String] path to private key
106
- # @param pub_key_pem [String] PEM of public key
107
- # @return [Hash] :preset_value, :test_args
108
- def wizard(object:, private_key_path:, pub_key_pem:)
109
- options = object.options
110
- formatter = object.formatter
111
- instance_url = options.get_option(:url, mandatory: true)
112
- wiz_username = options.get_option(:username, mandatory: true)
113
- raise "Username shall be an email in Faspex: #{wiz_username}" if !(wiz_username =~ /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i)
114
- if options.get_option(:client_id).nil? || options.get_option(:client_secret).nil?
115
- formatter.display_status('Ask the ascli client id and secret to your Administrator.'.red)
116
- formatter.display_status("Log in as an admin user at: #{instance_url}")
117
- Environment.instance.open_uri(instance_url)
118
- formatter.display_status('Navigate to: 𓃑 → Admin Configurations API clients')
119
- formatter.display_status('Create an API client with:')
120
- formatter.display_status('- name: ascli')
121
- formatter.display_status('- JWT: enabled')
122
- formatter.display_status("Log in as user #{wiz_username.red}. Navigate to your profile:")
123
- formatter.display_status('👤 → Account Settings → Preferences → Public Key in PEM:')
124
- formatter.display_status(pub_key_pem)
125
- formatter.display_status('Once set, fill in the parameters:')
126
- end
127
- return {preset_value: {}, test_args: ''} if options.get_option(:test_mode)
128
- return {
129
- preset_value: {
130
- url: instance_url,
131
- username: wiz_username,
132
- auth: :jwt.to_s,
133
- private_key: "@file:#{private_key_path}",
134
- client_id: options.get_option(:client_id, mandatory: true),
135
- client_secret: options.get_option(:client_secret, mandatory: true)
136
- },
137
- test_args: 'user profile show'
138
- }
139
- end
140
-
141
- # @return true if the URL is a public link
142
- def public_link?(url)
143
- url.include?('?context=')
54
+ # @param wizard [Wizard] The wizard object
55
+ # @param app_url [Wizard] The wizard object
56
+ # @return [Hash] :preset_value, :test_args
57
+ def wizard(wizard, app_url)
58
+ client_id = options.get_option(:client_id)
59
+ client_secret = options.get_option(:client_secret)
60
+ if client_id.nil? || client_secret.nil?
61
+ formatter.display_status('Ask the ascli client id and secret to your Administrator.'.red)
62
+ formatter.display_status("Log in as an admin user at: #{app_url}")
63
+ Environment.instance.open_uri(app_url)
64
+ formatter.display_status('Navigate to: 𓃑 → Admin → Configurations → API clients')
65
+ formatter.display_status('Create an API client with:')
66
+ formatter.display_status('- name: ascli')
67
+ formatter.display_status('- JWT: enabled')
68
+ formatter.display_status('Upon creation, the admin shall get those parameters:')
69
+ client_id = options.get_option(:client_id, mandatory: wizard.required)
70
+ client_secret = options.get_option(:client_secret, mandatory: wizard.required)
144
71
  end
72
+ wiz_username = options.get_option(:username, mandatory: true)
73
+ wizard.check_email(wiz_username)
74
+ private_key_path = wizard.ask_private_key(
75
+ user: wiz_username,
76
+ url: app_url,
77
+ page: '👤 → Account Settings → Preferences → Public Key in PEM'
78
+ )
79
+ return {
80
+ preset_value: {
81
+ url: app_url,
82
+ username: wiz_username,
83
+ auth: :jwt.to_s,
84
+ private_key: "@file:#{private_key_path}",
85
+ client_id: client_id,
86
+ client_secret: client_secret
87
+ },
88
+ test_args: 'user profile show'
89
+ }
145
90
  end
146
91
 
147
92
  def initialize(**_)
148
93
  super
149
- options.declare(:client_id, 'OAuth client identifier')
150
- options.declare(:client_secret, 'OAuth client secret')
151
- options.declare(:redirect_uri, 'OAuth redirect URI for web authentication')
152
- options.declare(:auth, 'OAuth type of authentication', values: STD_AUTH_TYPES, default: :jwt)
153
- options.declare(:private_key, 'OAuth JWT RSA private key PEM value (prefix file path with @file:)')
154
- options.declare(:passphrase, 'OAuth JWT RSA private key passphrase')
155
- options.declare(:box, "Package inbox, either shared inbox name or one of: #{API_LIST_MAILBOX_TYPES.join(', ')} or #{SpecialValues::ALL}", default: 'inbox')
94
+ options.declare(:box, "Package inbox, either shared inbox name or one of: #{Api::Faspex::API_LIST_MAILBOX_TYPES.join(', ')} or #{SpecialValues::ALL}", default: 'inbox')
156
95
  options.declare(:shared_folder, 'Send package with files from shared folder')
157
- options.declare(:group_type, 'Type of shared box', values: %i[shared_inboxes workgroups], default: :shared_inboxes)
96
+ options.declare(:group_type, 'Type of shared box', allowed: %i[shared_inboxes workgroups], default: :shared_inboxes)
158
97
  options.parse_options!
159
- @pub_link_context = nil
160
98
  end
161
99
 
162
100
  def set_api
163
- # get endpoint, remove unnecessary trailing slashes
164
- @faspex5_api_base_url = options.get_option(:url, mandatory: true).gsub(%r{/+$}, '')
165
- auth_type = self.class.public_link?(@faspex5_api_base_url) ? :public_link : options.get_option(:auth, mandatory: true)
166
- case auth_type
167
- when :public_link
168
- # resolve any redirect
169
- @faspex5_api_base_url = Rest.new(base_url: @faspex5_api_base_url, redirect_max: 3).call(operation: 'GET')[:http].uri.to_s
170
- encoded_context = Rest.query_to_h(URI.parse(@faspex5_api_base_url).query)['context']
171
- raise BadArgument, 'Bad faspex5 public link, missing context in query' if encoded_context.nil?
172
- # public link information (allowed usage)
173
- @pub_link_context = JSON.parse(Base64.decode64(encoded_context))
174
- Log.dump(:@pub_link_context, @pub_link_context, level: :trace1)
175
- # ok, we have the additional parameters, get the base url
176
- @faspex5_api_base_url = @faspex5_api_base_url.gsub(%r{/public/.*}, '').gsub(/\?.*/, '')
177
- @api_v5 = Rest.new(
178
- base_url: "#{@faspex5_api_base_url}/#{PATH_API_V5}",
179
- headers: {'Passcode' => @pub_link_context['passcode']}
180
- )
181
- when :boot
182
- # the password here is the token copied directly from browser in developer mode
183
- @api_v5 = Rest.new(
184
- base_url: "#{@faspex5_api_base_url}/#{PATH_API_V5}",
185
- headers: {'Authorization' => options.get_option(:password, mandatory: true)}
186
- )
187
- when :web
188
- # opens a browser and ask user to auth using web
189
- @api_v5 = Rest.new(
190
- base_url: "#{@faspex5_api_base_url}/#{PATH_API_V5}",
191
- auth: {
192
- type: :oauth2,
193
- base_url: "#{@faspex5_api_base_url}/#{PATH_AUTH}",
194
- grant_method: :web,
195
- client_id: options.get_option(:client_id, mandatory: true),
196
- redirect_uri: options.get_option(:redirect_uri, mandatory: true)
197
- }
198
- )
199
- when :jwt
200
- app_client_id = options.get_option(:client_id, mandatory: true)
201
- @api_v5 = Rest.new(
202
- base_url: "#{@faspex5_api_base_url}/#{PATH_API_V5}",
203
- auth: {
204
- type: :oauth2,
205
- grant_method: :jwt,
206
- base_url: "#{@faspex5_api_base_url}/#{PATH_AUTH}",
207
- client_id: app_client_id,
208
- payload: {
209
- iss: app_client_id, # issuer
210
- aud: app_client_id, # audience (this field is not clear...)
211
- sub: "user:#{options.get_option(:username, mandatory: true)}" # subject is a user
212
- },
213
- private_key_obj: OpenSSL::PKey::RSA.new(options.get_option(:private_key, mandatory: true), options.get_option(:passphrase)),
214
- headers: {typ: 'JWT'}
215
- }
216
- )
217
- else Aspera.error_unexpected_value(auth_type)
218
- end
101
+ # create an API object with the same options, but with a different subpath
102
+ @api_v5 = new_with_options(Api::Faspex)
219
103
  # in case user wants to use HTTPGW tell transfer agent how to get address
220
104
  transfer.httpgw_url_cb = lambda{@api_v5.read('account')['gateway_url']}
221
105
  end
@@ -224,7 +108,7 @@ module Aspera
224
108
  def normalize_recipients(parameters)
225
109
  return unless parameters.key?('recipients')
226
110
  Aspera.assert_type(parameters['recipients'], Array){'recipients'}
227
- recipient_types = RECIPIENT_TYPES
111
+ recipient_types = Api::Faspex::RECIPIENT_TYPES
228
112
  if parameters.key?('recipient_types')
229
113
  recipient_types = parameters['recipient_types']
230
114
  parameters.delete('recipient_types')
@@ -233,7 +117,7 @@ module Aspera
233
117
  parameters['recipients'].map! do |recipient_data|
234
118
  # if just a string, make a general lookup and build expected name/type hash
235
119
  if recipient_data.is_a?(String)
236
- matched = @api_v5.lookup_by_name('contacts', recipient_data, query: {context: 'packages', type: Rest.array_params(recipient_types)})
120
+ matched = @api_v5.lookup_by_name('contacts', recipient_data, query: Rest.php_style({context: 'packages', type: recipient_types}))
237
121
  recipient_data = {
238
122
  name: matched['name'],
239
123
  recipient_type: matched['type']
@@ -245,7 +129,7 @@ module Aspera
245
129
  end
246
130
 
247
131
  # wait for package status to be in provided list
248
- def wait_package_status(id, status_list: PACKAGE_TERMINATED)
132
+ def wait_package_status(id, status_list: Api::Faspex::PACKAGE_TERMINATED)
249
133
  total_sent = false
250
134
  loop do
251
135
  status = @api_v5.read("packages/#{id}/upload_details")
@@ -276,7 +160,7 @@ module Aspera
276
160
  result = nil
277
161
  loop do
278
162
  result = @api_v5.read("jobs/#{job_id}", {type: :formatted})
279
- break unless JOB_RUNNING.include?(result['status'])
163
+ break unless Api::Faspex::JOB_RUNNING.include?(result['status'])
280
164
  formatter.long_operation_running(result['status'])
281
165
  sleep(0.5)
282
166
  end
@@ -292,7 +176,7 @@ module Aspera
292
176
  entity =
293
177
  case box
294
178
  when SpecialValues::ALL then 'packages' # only admin can list all packages globally
295
- when *API_LIST_MAILBOX_TYPES then "#{box}/packages"
179
+ when *Api::Faspex::API_LIST_MAILBOX_TYPES then "#{box}/packages"
296
180
  else
297
181
  group_type = options.get_option(:group_type)
298
182
  "#{group_type}/#{lookup_entity_by_field(api: @api_v5, entity: group_type, value: box)['id']}/packages"
@@ -338,8 +222,7 @@ module Aspera
338
222
  else
339
223
  # a single id was provided, or a list of ids
340
224
  package_ids = [package_ids] unless package_ids.is_a?(Array)
341
- Aspera.assert_type(package_ids, Array){'Expecting a single package id or a list of ids'}
342
- Aspera.assert(package_ids.all?(String)){'Package id shall be String'}
225
+ Aspera.assert_array_all(package_ids, String){'Package id(s)'}
343
226
  # packages = package_ids.map{|pkg_id|@api_v5.read("packages/#{pkg_id}")}
344
227
  packages = package_ids.map{ |pkg_id| {'id'=>pkg_id}}
345
228
  end
@@ -352,12 +235,12 @@ module Aspera
352
235
  end
353
236
  download_params = {
354
237
  type: 'received',
355
- transfer_type: TRANSFER_CONNECT
238
+ transfer_type: Api::Faspex::TRANSFER_CONNECT
356
239
  }
357
240
  box = options.get_option(:box)
358
241
  case box
359
242
  when /outbox/ then download_params[:type] = 'sent'
360
- when *API_LIST_MAILBOX_TYPES then nil # nothing to do
243
+ when *Api::Faspex::API_LIST_MAILBOX_TYPES then nil # nothing to do
361
244
  else # shared inbox / workgroup
362
245
  download_params[:recipient_workgroup_id] = lookup_entity_by_field(api: @api_v5, entity: options.get_option(:group_type), value: box)['id']
363
246
  end
@@ -372,7 +255,7 @@ module Aspera
372
255
  content_type: Rest::MIME_JSON,
373
256
  body: param_file_list,
374
257
  headers: {'Accept' => Rest::MIME_JSON}
375
- )[:data]
258
+ )
376
259
  # delete flag for Connect Client
377
260
  transfer_spec.delete('authentication')
378
261
  statuses = transfer.start(transfer_spec)
@@ -410,28 +293,29 @@ module Aspera
410
293
  until folders_to_process.empty?
411
294
  path = folders_to_process.shift
412
295
  loop do
413
- response = @api_v5.call(
296
+ data, http = @api_v5.call(
414
297
  operation: 'POST',
415
298
  subpath: browse_endpoint,
416
299
  query: query,
417
300
  content_type: Rest::MIME_JSON,
418
301
  body: {'path' => path, 'filters' => filters},
419
- headers: {'Accept' => Rest::MIME_JSON}
302
+ headers: {'Accept' => Rest::MIME_JSON},
303
+ ret: :both
420
304
  )
421
- all_items.concat(response[:data]['items'])
305
+ all_items.concat(data['items'])
422
306
  if !max_items.nil? && (all_items.count >= max_items)
423
307
  all_items = all_items.slice(0, max_items) if all_items.count > max_items
424
308
  break
425
309
  end
426
- folders_to_process.concat(response[:data]['items'].select{ |i| i['type'].eql?('directory')}.map{ |i| i['path']}) if recursive
310
+ folders_to_process.concat(data['items'].select{ |i| i['type'].eql?('directory')}.map{ |i| i['path']}) if recursive
427
311
  if use_paging
428
- iteration_token = response[:http][HEADER_ITERATION_TOKEN]
312
+ iteration_token = http[Api::Faspex::HEADER_ITERATION_TOKEN]
429
313
  break if iteration_token.nil? || iteration_token.empty?
430
314
  query['iteration_token'] = iteration_token
431
315
  else
432
- total_count = response[:data]['total_count'] if total_count.nil?
433
- break if response[:data]['item_count'].eql?(0)
434
- query['offset'] += response[:data]['item_count']
316
+ total_count = data['total_count'] if total_count.nil?
317
+ break if data['item_count'].eql?(0)
318
+ query['offset'] += data['item_count']
435
319
  end
436
320
  formatter.long_operation_running(all_items.count)
437
321
  end
@@ -446,7 +330,7 @@ module Aspera
446
330
  command = options.get_next_command(%i[show browse status delete receive send list])
447
331
  package_id =
448
332
  if %i[receive show browse status delete].include?(command)
449
- @pub_link_context&.key?('package_id') ? @pub_link_context['package_id'] : instance_identifier
333
+ @api_v5.pub_link_context&.key?('package_id') ? @api_v5.pub_link_context['package_id'] : instance_identifier
450
334
  end
451
335
  case command
452
336
  when :show
@@ -465,8 +349,7 @@ module Aspera
465
349
  when :delete
466
350
  ids = package_id
467
351
  ids = [ids] unless ids.is_a?(Array)
468
- Aspera.assert_type(ids, Array){'Package identifier'}
469
- Aspera.assert(ids.all?(String)){"Package id(s) shall be String, but have: #{ids.map(&:class).uniq.join(', ')}"}
352
+ Aspera.assert_array_all(ids, String){'Package id(s)'}
470
353
  # API returns 204, empty on success
471
354
  @api_v5.call(
472
355
  operation: 'DELETE',
@@ -481,36 +364,39 @@ module Aspera
481
364
  when :send
482
365
  parameters = value_create_modify(command: command)
483
366
  # autofill recipient for public url
484
- if @pub_link_context&.key?('recipient_type') && !parameters.key?('recipients')
367
+ if @api_v5.pub_link_context&.key?('recipient_type') && !parameters.key?('recipients')
485
368
  parameters['recipients'] = [{
486
- name: @pub_link_context['name'],
487
- recipient_type: @pub_link_context['recipient_type']
369
+ name: @api_v5.pub_link_context['name'],
370
+ recipient_type: @api_v5.pub_link_context['recipient_type']
488
371
  }]
489
372
  end
490
373
  normalize_recipients(parameters)
374
+ # User specified content prot in tspec, but faspex requires in package creation
375
+ # `transfer_spec/upload` will set `content_protection`
376
+ if transfer.user_transfer_spec['content_protection'] && !parameters.key?('ear_enabled')
377
+ transfer.user_transfer_spec.delete('content_protection')
378
+ parameters['ear_enabled'] = true
379
+ end
491
380
  package = @api_v5.create('packages', parameters)
492
381
  shared_folder = options.get_option(:shared_folder)
493
382
  if shared_folder.nil?
494
383
  # send from local files
495
- transfer_spec = @api_v5.call(
496
- operation: 'POST',
497
- subpath: "packages/#{package['id']}/transfer_spec/upload",
498
- query: {transfer_type: TRANSFER_CONNECT},
499
- content_type: Rest::MIME_JSON,
500
- body: {paths: transfer.source_list},
501
- headers: {'Accept' => Rest::MIME_JSON}
502
- )[:data]
384
+ transfer_spec = @api_v5.create(
385
+ "packages/#{package['id']}/transfer_spec/upload",
386
+ {paths: transfer.source_list},
387
+ query: {transfer_type: Api::Faspex::TRANSFER_CONNECT}
388
+ )
503
389
  # well, we asked a TS for connect, but we actually want a generic one
504
390
  transfer_spec.delete('authentication')
505
391
  return Main.result_transfer(transfer.start(transfer_spec))
506
392
  else
507
393
  # send from remote shared folder
508
- if (m = shared_folder.match(REGEX_LOOKUP_ID_BY_FIELD))
394
+ if (m = Base.percent_selector(shared_folder))
509
395
  shared_folder = lookup_entity_by_field(
510
396
  api: @api_v5,
511
397
  entity: 'shared_folders',
512
- field: m[1],
513
- value: ExtendedValue.instance.evaluate(m[2])
398
+ field: m[:field],
399
+ value: m[:value]
514
400
  )['id']
515
401
  end
516
402
  transfer_request = {shared_folder_id: shared_folder, paths: transfer.source_list}
@@ -536,7 +422,7 @@ module Aspera
536
422
  tclo: true
537
423
  }
538
424
  res_id_query = :default
539
- available_commands = Plugin::ALL_OPS
425
+ available_commands = ALL_OPS
540
426
  case res_sym
541
427
  when :metadata_profiles
542
428
  exec_args[:entity] = 'configuration/metadata_profiles'
@@ -554,7 +440,7 @@ module Aspera
554
440
  available_commands += [:reset_password]
555
441
  when :oauth_clients
556
442
  exec_args[:display_fields] = Formatter.all_but('public_key')
557
- exec_args[:api] = Rest.new(**@api_v5.params, base_url: "#{@faspex5_api_base_url}/#{PATH_AUTH}")
443
+ exec_args[:api] = @api_v5.auth_api
558
444
  exec_args[:list_query] = {'expand': true, 'no_api_path': true, 'client_types[]': 'public'}
559
445
  when :shared_inboxes, :workgroups
560
446
  available_commands += %i[members saml_groups invite_external_collaborator]
@@ -563,9 +449,9 @@ module Aspera
563
449
  available_commands += %i[shared_folders browse]
564
450
  end
565
451
  res_command = options.get_next_command(available_commands)
566
- return Main.result_value_list(EMAIL_NOTIF_LIST, name: 'email_id') if res_command.eql?(:list) && res_sym.eql?(:email_notifications)
452
+ return Main.result_value_list(Api::Faspex::EMAIL_NOTIF_LIST, name: 'email_id') if res_command.eql?(:list) && res_sym.eql?(:email_notifications)
567
453
  case res_command
568
- when *Plugin::ALL_OPS
454
+ when *ALL_OPS
569
455
  return entity_execute(command: res_command, **exec_args) do |field, value|
570
456
  lookup_entity_by_field(api: @api_v5, entity: exec_args[:entity], value: value, field: field, items_key: exec_args[:items_key], query: res_id_query)['id']
571
457
  end
@@ -575,9 +461,9 @@ module Aspera
575
461
  lookup_entity_by_field(api: @api_v5, entity: 'nodes', field: field, value: value)['id']
576
462
  end
577
463
  shfld_entity = "nodes/#{node_id}/shared_folders"
578
- sh_command = options.get_next_command(Plugin::ALL_OPS + [:user])
464
+ sh_command = options.get_next_command(ALL_OPS + [:user])
579
465
  case sh_command
580
- when *Plugin::ALL_OPS
466
+ when *ALL_OPS
581
467
  return entity_execute(
582
468
  api: @api_v5,
583
469
  entity: shfld_entity,
@@ -628,20 +514,20 @@ module Aspera
628
514
  users = options.get_next_argument('user id, %name:, or Array')
629
515
  users = [users] unless users.is_a?(Array)
630
516
  users = users.map do |user|
631
- if (m = user.match(REGEX_LOOKUP_ID_BY_FIELD))
517
+ if (m = Base.percent_selector(user))
632
518
  lookup_entity_by_field(
633
519
  api: @api_v5,
634
520
  entity: 'accounts',
635
- field: m[1],
636
- value: ExtendedValue.instance.evaluate(m[2]),
637
- query: {type: Rest.array_params(%w{local_user saml_user self_registered_user external_user})}
521
+ field: m[:field],
522
+ value: m[:value],
523
+ query: Rest.php_style({type: ACCOUNT_TYPES})
638
524
  )['id']
639
525
  else
640
526
  # it's the user id (not member id...)
641
527
  user
642
528
  end
643
529
  end
644
- access = options.get_next_argument('level', mandatory: false, accept_list: %i[submit_only standard shared_inbox_admin], default: :standard)
530
+ access = options.get_next_argument('level', mandatory: false, accept_list: SHARED_INBOX_MEMBER_LEVELS, default: :standard)
645
531
  options.unshift_next_argument({user: users.map{ |u| {id: u, access: access}}})
646
532
  end
647
533
  return entity_execute(
@@ -652,10 +538,10 @@ module Aspera
652
538
  ) do |field, value|
653
539
  lookup_entity_by_field(
654
540
  api: @api_v5,
655
- entity: 'accounts',
541
+ entity: 'contacts',
656
542
  field: field,
657
543
  value: value,
658
- query: {type: Rest.array_params(%w{local_user saml_user self_registered_user external_user})}
544
+ query: Rest.php_style({type: %w[user]})
659
545
  )['id']
660
546
  end
661
547
  when :reset_password
@@ -668,13 +554,9 @@ module Aspera
668
554
  end
669
555
 
670
556
  def execute_admin
671
- command = options.get_next_command(%i[configuration smtp resource events clean_deleted].concat(ADMIN_RESOURCES).freeze)
557
+ command = options.get_next_command(%i[configuration smtp events clean_deleted].concat(Api::Faspex::ADMIN_RESOURCES).freeze)
672
558
  case command
673
- when :resource
674
- # resource will be deprecated
675
- Log.log.warn('resource command is deprecated (4.18), directly use the specific command instead')
676
- return execute_resource(options.get_next_command(ADMIN_RESOURCES))
677
- when *ADMIN_RESOURCES
559
+ when *Api::Faspex::ADMIN_RESOURCES
678
560
  return execute_resource(command)
679
561
  when :clean_deleted
680
562
  delete_data = value_create_modify(command: command, default: {})
@@ -739,25 +621,27 @@ module Aspera
739
621
 
740
622
  def execute_action
741
623
  command = options.get_next_command(ACTIONS)
742
- set_api unless command.eql?(:postprocessing)
624
+ set_api unless %i{postprocessing health}.include?(command)
743
625
  case command
744
626
  when :version
745
627
  return Main.result_single_object(@api_v5.read('version'))
746
628
  when :health
747
629
  nagios = Nagios.new
748
630
  begin
749
- result = Rest.new(base_url: @faspex5_api_base_url).read('health')
750
- result.each do |k, v|
631
+ data, http = Rest.new(base_url: options.get_option(:url, mandatory: true))
632
+ .read('health', ret: :both)
633
+ data.each do |k, v|
751
634
  nagios.add_ok(k, v.to_s)
752
635
  end
636
+ nagios.add_ok('version', http['X-IBM-Aspera']) if http['X-IBM-Aspera']
753
637
  rescue StandardError => e
754
- nagios.add_critical('faspex api', e.to_s)
638
+ nagios.add_critical('core', e.to_s)
755
639
  end
756
- return nagios.result
640
+ Main.result_object_list(nagios.status_list)
757
641
  when :user
758
642
  case options.get_next_command(%i[account profile])
759
643
  when :account
760
- return Main.result_single_object(@api_v5.read('account'))
644
+ return Main.result_single_object(@api_v5.read('account', query_read_delete))
761
645
  when :profile
762
646
  case options.get_next_command(%i[show modify])
763
647
  when :show
@@ -791,7 +675,7 @@ module Aspera
791
675
  return execute_admin
792
676
  when :invitations
793
677
  invitation_endpoint = 'invitations'
794
- invitation_command = options.get_next_command(%i[resend].concat(Plugin::ALL_OPS))
678
+ invitation_command = options.get_next_command(%i[resend].concat(ALL_OPS))
795
679
  case invitation_command
796
680
  when :create
797
681
  return do_bulk_operation(command: invitation_command, descr: 'data') do |params|
@@ -832,6 +716,10 @@ module Aspera
832
716
  return Main.result_status('Gateway terminated')
833
717
  end
834
718
  end
719
+ SHARED_INBOX_MEMBER_LEVELS = %i[submit_only standard shared_inbox_admin].freeze
720
+ ACCOUNT_TYPES = %w{local_user saml_user self_registered_user external_user}.freeze
721
+ CONTACT_TYPES = %w{workgroup shared_inbox distribution_list user external_user}.freeze
722
+ private_constant :SHARED_INBOX_MEMBER_LEVELS, :ACCOUNT_TYPES, :CONTACT_TYPES
835
723
  end
836
724
  end
837
725
  end