aspera-cli 4.25.6 → 4.26.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 (64) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +89 -48
  4. data/CONTRIBUTING.md +1 -1
  5. data/lib/aspera/api/aoc.rb +120 -79
  6. data/lib/aspera/api/node.rb +103 -51
  7. data/lib/aspera/ascp/installation.rb +99 -32
  8. data/lib/aspera/assert.rb +17 -13
  9. data/lib/aspera/cli/extended_value.rb +7 -2
  10. data/lib/aspera/cli/formatter.rb +107 -95
  11. data/lib/aspera/cli/main.rb +69 -10
  12. data/lib/aspera/cli/manager.rb +158 -78
  13. data/lib/aspera/cli/options.schema.yaml +82 -0
  14. data/lib/aspera/cli/plugins/aoc.rb +247 -144
  15. data/lib/aspera/cli/plugins/ats.rb +3 -3
  16. data/lib/aspera/cli/plugins/base.rb +60 -76
  17. data/lib/aspera/cli/plugins/config.rb +14 -12
  18. data/lib/aspera/cli/plugins/console.rb +3 -3
  19. data/lib/aspera/cli/plugins/faspex.rb +6 -6
  20. data/lib/aspera/cli/plugins/faspex5.rb +24 -23
  21. data/lib/aspera/cli/plugins/node.rb +67 -71
  22. data/lib/aspera/cli/plugins/oauth.rb +5 -12
  23. data/lib/aspera/cli/plugins/orchestrator.rb +13 -13
  24. data/lib/aspera/cli/plugins/preview.rb +116 -80
  25. data/lib/aspera/cli/plugins/server.rb +2 -10
  26. data/lib/aspera/cli/plugins/shares.rb +7 -7
  27. data/lib/aspera/cli/sync_actions.rb +1 -1
  28. data/lib/aspera/cli/transfer_agent.rb +17 -15
  29. data/lib/aspera/cli/version.rb +1 -1
  30. data/lib/aspera/command_line_builder.rb +22 -18
  31. data/lib/aspera/dot_container.rb +7 -3
  32. data/lib/aspera/environment.rb +6 -5
  33. data/lib/aspera/formatter_interface.rb +14 -0
  34. data/lib/aspera/hash_ext.rb +6 -0
  35. data/lib/aspera/log.rb +5 -4
  36. data/lib/aspera/markdown.rb +4 -1
  37. data/lib/aspera/oauth/factory.rb +1 -1
  38. data/lib/aspera/preview/file_types.rb +1 -1
  39. data/lib/aspera/preview/generator.rb +146 -91
  40. data/lib/aspera/preview/options.rb +4 -1
  41. data/lib/aspera/preview/terminal.rb +50 -20
  42. data/lib/aspera/preview/utils.rb +76 -34
  43. data/lib/aspera/products/transferd.rb +1 -1
  44. data/lib/aspera/proxy_auto_config.rb +3 -0
  45. data/lib/aspera/rest.rb +2 -1
  46. data/lib/aspera/rest_list.rb +23 -16
  47. data/lib/aspera/schema/IBM Aspera Faspex API-5.0-enhanced.yaml +62801 -0
  48. data/lib/aspera/schema/IBM Aspera on Cloud API-0.2.6-enhanced.yaml +8898 -0
  49. data/lib/aspera/schema/documentation.rb +107 -0
  50. data/lib/aspera/schema/reader.rb +75 -0
  51. data/lib/aspera/schema/registry.rb +63 -0
  52. data/lib/aspera/secret_hider.rb +3 -1
  53. data/lib/aspera/sync/conf.schema.yaml +0 -26
  54. data/lib/aspera/sync/operations.rb +9 -5
  55. data/lib/aspera/transfer/faux_file.rb +1 -1
  56. data/lib/aspera/transfer/resumer.rb +1 -1
  57. data/lib/aspera/transfer/spec.rb +3 -3
  58. data/lib/aspera/transfer/spec.schema.yaml +1 -1
  59. data/lib/aspera/uri_reader.rb +17 -2
  60. data/lib/aspera/yaml.rb +4 -2
  61. data.tar.gz.sig +0 -0
  62. metadata +13 -7
  63. metadata.gz.sig +0 -0
  64. data/lib/aspera/transfer/spec_doc.rb +0 -76
@@ -12,12 +12,52 @@ require 'base64'
12
12
 
13
13
  module Aspera
14
14
  module Api
15
+ # information relative to Files or Packages App
16
+
15
17
  # Aspera on Cloud API client
16
18
  class AoC < Rest
17
19
  include RestList
18
20
 
21
+ module AppType
22
+ FILES = 'files'
23
+ PACKAGES = 'packages'
24
+ end
25
+
26
+ class AppInfo
27
+ # @return [Aspera::Api::AoC] Rest Api object
28
+ attr_reader :api
29
+ # @return [Hash, nil] Package information, `"files"` or `"packages"`
30
+ attr_reader :package
31
+ # @return [Hash] Result of GET /nodes/:id
32
+ attr_reader :node_info
33
+ # @return [String] Workspace id
34
+ attr_reader :workspace_id
35
+ # @return [String] Workspace name
36
+ attr_reader :workspace_name
37
+ # @return [String, nil] "Share as" link name
38
+ attr_accessor :opt_link_name
39
+
40
+ # @param api [Api::Aoc] Rest Api object
41
+ # @param package [Hash,nil] package information or nil
42
+ # @param node_info [Hash] Result of GET /nodes/:id
43
+ # @param workspace_id [String]
44
+ # @param workspace_name [String]
45
+ def initialize(api, package, node_info, workspace_id, workspace_name)
46
+ @api = api
47
+ @package = package
48
+ @node_info = node_info
49
+ @workspace_id = workspace_id
50
+ @workspace_name = workspace_name
51
+ @opt_link_name = nil
52
+ end
53
+
54
+ # @return ["files", "packages"] Type of application
55
+ def app
56
+ package.nil? ? AppType::FILES : AppType::PACKAGES
57
+ end
58
+ end
19
59
  PRODUCT_NAME = 'Aspera on Cloud'
20
- # use default workspace if it is set, else none
60
+ # `''` Use default workspace if it is set, else none
21
61
  DEFAULT_WORKSPACE = ''
22
62
  # Production domain of AoC
23
63
  SAAS_DOMAIN_PROD = 'ibmaspera.com' # cspell:disable-line
@@ -39,6 +79,7 @@ module Aspera
39
79
  PERMISSIONS_CREATED = ['permission.created'].freeze
40
80
  # Special user identifier when creating workspace shared folders
41
81
  ID_AK_ADMIN = 'ASPERA_ACCESS_KEY_ADMIN'
82
+ HEADER_X_TOTAL_COUNT = 'X-Total-Count'
42
83
 
43
84
  private_constant :MAX_AOC_URL_REDIRECT,
44
85
  :CLIENT_ID_PREFIX,
@@ -49,18 +90,19 @@ module Aspera
49
90
  :OAUTH_API_SUBPATH,
50
91
  :USER_INFO_FIELDS_MIN,
51
92
  :PERMISSIONS_CREATED,
52
- :ID_AK_ADMIN
93
+ :ID_AK_ADMIN,
94
+ :HEADER_X_TOTAL_COUNT
53
95
 
54
96
  # Various API scopes supported
55
97
  module Scope
56
98
  SELF = 'self'
99
+ # Scope `user:all`
57
100
  USER = 'user:all'
101
+ # Scope `admin:all`
58
102
  ADMIN = 'admin:all'
59
103
  ADMIN_USER = 'admin-user:all'
60
104
  ADMIN_USER_USER = "#{ADMIN_USER}+#{USER}"
61
105
  end
62
- FILES_APP = 'files'
63
- PACKAGES_APP = 'packages'
64
106
  API_V1 = 'api/v1'
65
107
 
66
108
  # class static methods
@@ -92,8 +134,9 @@ module Aspera
92
134
  false
93
135
  end
94
136
 
137
+ # Get information from link
95
138
  # @param url [String] URL of AoC public link
96
- # @return [Hash] information about public link, or nil if not a public link
139
+ # @return [Hash{Symbol => String, Hash}] Information about public link, or nil if not a public link
97
140
  def link_info(url)
98
141
  final_uri = Rest.new(base_url: url, redirect_max: MAX_AOC_URL_REDIRECT).call(operation: 'GET', ret: :resp).uri
99
142
  Log.dump(:final_uri, final_uri, level: :trace1)
@@ -169,7 +212,7 @@ module Aspera
169
212
  new_query['page'] = current_page
170
213
  result_data, result_http = yield(new_query)
171
214
  Aspera.assert(result_http)
172
- total_count = result_http['X-Total-Count']&.to_i
215
+ total_count = result_http[HEADER_X_TOTAL_COUNT]&.to_i
173
216
  page_count += 1
174
217
  current_page += 1
175
218
  add_items = result_data
@@ -221,21 +264,23 @@ module Aspera
221
264
  end
222
265
 
223
266
  attr_reader :private_link
267
+ attr_accessor :ws_ids
224
268
 
269
+ # By default: no workspace
225
270
  def initialize(
271
+ scope: nil,
272
+ subpath: API_V1,
273
+ secret_finder: nil,
274
+ # below: OAuth::AUTH_OPTIONS
226
275
  url:,
227
276
  auth:,
228
- subpath: API_V1,
229
277
  client_id: nil,
230
278
  client_secret: nil,
231
- scope: nil,
232
279
  redirect_uri: nil,
233
280
  private_key: nil,
234
281
  passphrase: nil,
235
282
  username: nil,
236
- password: nil,
237
- workspace: nil,
238
- secret_finder: nil
283
+ password: nil
239
284
  )
240
285
  # Test here because link may set url
241
286
  Aspera.assert(url, 'Missing mandatory option: url', type: ParameterError)
@@ -246,10 +291,12 @@ module Aspera
246
291
  # key: access key
247
292
  # value: associated secret
248
293
  @secret_finder = secret_finder
249
- @workspace_name = workspace
250
294
  @cache_user_info = nil
251
295
  @cache_url_token_info = nil
296
+ # @type [Hash]
252
297
  @workspace_info = nil
298
+ # Used only for init: provided by user
299
+ @ws_ids = {id: nil, name: nil}
253
300
  @home_info = nil
254
301
  auth_params = {
255
302
  type: :oauth2,
@@ -315,6 +362,7 @@ module Aspera
315
362
  end
316
363
  end
317
364
 
365
+ # @param expected [Array<String>] Link types
318
366
  def assert_public_link_types(expected)
319
367
  Aspera.assert_values(public_link['purpose'], expected){'public link type'}
320
368
  end
@@ -350,34 +398,38 @@ module Aspera
350
398
  return @cache_user_info
351
399
  end
352
400
 
353
- # Cached workspace information
354
- def workspace
401
+ def default_workspace?
402
+ @ws_ids[:name].eql?(DEFAULT_WORKSPACE)
403
+ end
404
+
405
+ # Cached workspace information.
406
+ # Always with `:name`.
407
+ # If no workspace, then no `:id`
408
+ # @return [Hash{Symbol=>String}] Workspace info.
409
+ def workspace_info
355
410
  return @workspace_info unless @workspace_info.nil?
356
- ws_id =
357
- if !public_link.nil?
358
- Log.log.debug('Using workspace of public link')
359
- public_link['data']['workspace_id']
360
- elsif !private_link.nil?
361
- Log.log.debug('Using workspace of private link')
362
- private_link[:workspace_id]
363
- elsif @workspace_name.eql?(DEFAULT_WORKSPACE)
364
- if !current_user_info['default_workspace_id'].nil?
365
- Log.log.debug('Using default workspace'.green)
366
- current_user_info['default_workspace_id']
367
- end
368
- elsif @workspace_name.nil?
369
- nil
411
+ if !public_link.nil?
412
+ Log.log.debug('Using workspace of public link')
413
+ @ws_ids[:id] = public_link['data']['workspace_id']
414
+ elsif !private_link.nil?
415
+ Log.log.debug('Using workspace of private link')
416
+ @ws_ids[:id] = private_link[:workspace_id]
417
+ elsif @ws_ids[:id]
418
+ Log.log.debug("Using workspace id: #{@ws_ids[:id]}")
419
+ elsif default_workspace?
420
+ if current_user_info['default_workspace_id'].nil?
421
+ @ws_ids[:name] = nil
422
+ Log.log.warn('No default workspace')
370
423
  else
371
- lookup_by_name('workspaces', @workspace_name)['id']
372
- end
373
- @workspace_info =
374
- if ws_id.nil?
375
- {
376
- name: 'Shared (no workspace)'
377
- }
378
- else
379
- read("workspaces/#{ws_id}").slice('id', 'name', 'home_node_id', 'home_file_id').symbolize_keys
424
+ Log.log.debug('Using default workspace'.green)
425
+ @ws_ids[:id] = current_user_info['default_workspace_id']
380
426
  end
427
+ elsif !@ws_ids[:name].nil?
428
+ @workspace_info = lookup_with_q('workspaces', value: @ws_ids[:name])
429
+ end
430
+ @workspace_info = read("workspaces/#{@ws_ids[:id]}") unless @ws_ids[:id].nil?
431
+ @workspace_info ||= {name: 'Shared (no workspace)'}
432
+ @workspace_info = @workspace_info.slice('name', 'id', 'home_node_id', 'home_file_id').symbolize_keys
381
433
  Log.dump(:workspace_info, @workspace_info)
382
434
  @workspace_info
383
435
  end
@@ -397,10 +449,10 @@ module Aspera
397
449
  node_id: private_link[:node_id],
398
450
  file_id: private_link[:file_id]
399
451
  }
400
- elsif workspace[:home_node_id] && workspace[:home_file_id]
452
+ elsif workspace_info[:home_node_id] && workspace_info[:home_file_id]
401
453
  {
402
- node_id: workspace[:home_node_id],
403
- file_id: workspace[:home_file_id]
454
+ node_id: workspace_info[:home_node_id],
455
+ file_id: workspace_info[:home_file_id]
404
456
  }
405
457
  else
406
458
  # not part of any workspace, but has some folder shared
@@ -426,18 +478,7 @@ module Aspera
426
478
  Aspera.assert_type(node_id, String)
427
479
  node_info = read("nodes/#{node_id}")
428
480
  workspace_name = read("workspaces/#{workspace_id}")['name'] if workspace_name.nil? && !workspace_id.nil?
429
- app_info = {
430
- api: self, # for callback
431
- app: package_info.nil? ? FILES_APP : PACKAGES_APP,
432
- node_info: node_info,
433
- workspace_id: workspace_id,
434
- workspace_name: workspace_name
435
- }
436
- if PACKAGES_APP.eql?(app_info[:app])
437
- Aspera.assert(!package_info.nil?){'package info required'}
438
- app_info[:package_id] = package_info['id']
439
- app_info[:package_name] = package_info['name']
440
- end
481
+ app_info = AppInfo.new(self, package_info, node_info, workspace_id, workspace_name)
441
482
  node_params = {base_url: node_info['url']}
442
483
  ak_secret = @secret_finder&.lookup_secret(url: node_info['url'], username: node_info['access_key'])
443
484
  # If secret is available, or no scope, use basic auth
@@ -514,10 +555,10 @@ module Aspera
514
555
  # email: user, else dropbox
515
556
  entity_type = short_recipient_info.include?('@') ? 'contacts' : 'dropboxes'
516
557
  begin
517
- full_recipient_info = lookup_by_name(entity_type, short_recipient_info, query: {'workspace_id' => ws_id})
558
+ full_recipient_info = lookup_with_q(entity_type, value: short_recipient_info, query: {'workspace_id' => ws_id})
518
559
  rescue EntityNotFound
519
560
  # dropboxes cannot be created on the fly
520
- Aspera.assert_values(entity_type, 'contacts', type: Error){"No such shared inbox in workspace #{ws_id}"}
561
+ Aspera.assert_values(entity_type, %w[contacts], type: Error){"No such shared inbox in workspace #{ws_id}"}
521
562
  # unknown user: create it as external user
522
563
  full_recipient_info = create('contacts', {
523
564
  'current_workspace_id' => ws_id,
@@ -609,13 +650,13 @@ module Aspera
609
650
  transfer_spec.deep_merge!({
610
651
  'tags' => {
611
652
  Transfer::Spec::TAG_RESERVED => {
612
- 'app' => app_info[:app],
613
- 'usage_id' => "aspera.files.workspace.#{app_info[:workspace_id]}", # activity tracking
653
+ 'app' => app_info.app,
654
+ 'usage_id' => "aspera.files.workspace.#{app_info.workspace_id}", # activity tracking
614
655
  'files' => {
615
- 'node_id' => app_info[:node_info]['id'],
616
- 'files_transfer_action' => "#{transfer_type}_#{app_info[:app].gsub(/s$/, '')}",
617
- 'workspace_name' => app_info[:workspace_name], # activity tracking
618
- 'workspace_id' => app_info[:workspace_id]
656
+ 'node_id' => app_info.node_info['id'],
657
+ 'files_transfer_action' => "#{transfer_type}_#{app_info.app.gsub(/s$/, '')}",
658
+ 'workspace_name' => app_info.workspace_name, # activity tracking
659
+ 'workspace_id' => app_info.workspace_id
619
660
  }
620
661
  }
621
662
  }
@@ -623,30 +664,30 @@ module Aspera
623
664
  # Console cookie
624
665
  ################
625
666
  # we are sure that fields are not nil
626
- cookie_elements = [app_info[:app], current_user_info['name'] || 'public link', current_user_info['email'] || 'none'].map{ |e| Base64.strict_encode64(e)}
667
+ cookie_elements = [app_info.app, current_user_info['name'] || 'public link', current_user_info['email'] || 'none'].map{ |e| Base64.strict_encode64(e)}
627
668
  cookie_elements.unshift(COOKIE_PREFIX_CONSOLE_AOC)
628
669
  transfer_spec['cookie'] = cookie_elements.join(':')
629
670
  # Application tags
630
671
  ##################
631
- case app_info[:app]
632
- when FILES_APP
672
+ case app_info.app
673
+ when AppType::FILES
633
674
  file_id = transfer_spec['tags'][Transfer::Spec::TAG_RESERVED]['node']['file_id']
634
675
  transfer_spec.deep_merge!({
635
676
  'tags' => {
636
677
  Transfer::Spec::TAG_RESERVED => {
637
678
  'files' => {
638
- 'parentCwd' => "#{app_info[:node_info]['id']}:#{file_id}"
679
+ 'parentCwd' => "#{app_info.node_info['id']}:#{file_id}"
639
680
  }
640
681
  }
641
682
  }
642
683
  }) unless transfer_spec.key?('remote_access_key')
643
- when PACKAGES_APP
684
+ when AppType::PACKAGES
644
685
  transfer_spec.deep_merge!({
645
686
  'tags' => {
646
687
  Transfer::Spec::TAG_RESERVED => {
647
688
  'files' => {
648
- 'package_id' => app_info[:package_id],
649
- 'package_name' => app_info[:package_name],
689
+ 'package_id' => app_info.package['id'],
690
+ 'package_name' => app_info.package['name'],
650
691
  'package_operation' => transfer_type
651
692
  }
652
693
  }
@@ -658,21 +699,21 @@ module Aspera
658
699
  # Callback from Plugins::Node
659
700
  # add application specific tags to permissions creation
660
701
  # @param perm_data [Hash] parameters for creating permissions
661
- # @param app_info [Hash] application information
702
+ # @param app_info [AppInfo] application information
662
703
  def permissions_set_create_params(perm_data:, app_info:)
663
704
  defaults = {
664
705
  'tags' => {
665
706
  Transfer::Spec::TAG_RESERVED => {
666
707
  'files' => {
667
708
  'workspace' => {
668
- 'id' => app_info[:workspace_id],
669
- 'workspace_name' => app_info[:workspace_name],
709
+ 'id' => app_info.workspace_id,
710
+ 'workspace_name' => app_info.workspace_name,
670
711
  'user_name' => current_user_info['name'],
671
712
  'shared_by_user_id' => current_user_info['id'],
672
713
  'shared_by_name' => current_user_info['name'],
673
714
  'shared_by_email' => current_user_info['email'],
674
- 'access_key' => app_info[:node_info]['access_key'],
675
- 'node' => app_info[:node_info]['name']
715
+ 'access_key' => app_info.node_info['access_key'],
716
+ 'node' => app_info.node_info['name']
676
717
  }
677
718
  }
678
719
  }
@@ -685,10 +726,10 @@ module Aspera
685
726
  when NilClass
686
727
  when ''
687
728
  # workspace shared folder
688
- perm_data.merge!(self.class.workspace_access(app_info[:workspace_id]))
729
+ perm_data.merge!(self.class.workspace_access(app_info.workspace_id))
689
730
  tag_workspace['shared_with_name'] = perm_data['access_id']
690
731
  else
691
- entity_info = lookup_by_name('contacts', shared_with, query: {'current_workspace_id' => app_info[:workspace_id]})
732
+ entity_info = lookup_with_q('contacts', value: shared_with, query: {'current_workspace_id' => app_info.workspace_id})
692
733
  perm_data['access_type'] = entity_info['source_type']
693
734
  perm_data['access_id'] = entity_info['source_id']
694
735
  tag_workspace['shared_with_name'] = entity_info['email'] # TODO: check that ???
@@ -698,26 +739,26 @@ module Aspera
698
739
  perm_data.delete('as')
699
740
  end
700
741
  # optional
701
- app_info[:opt_link_name] = perm_data.delete('link_name')
742
+ app_info.opt_link_name = perm_data.delete('link_name')
702
743
  end
703
744
 
704
745
  # Callback from Plugins::Node
705
746
  # send shared folder event to AoC
706
747
  # @param event_data [Hash] response from permission creation
707
- # @param app_info [Hash] hash with app info
748
+ # @param app_info [AppInfo] hash with app info
708
749
  # @param types [Array] event types
709
750
  def permissions_send_event(event_data:, app_info:, types: PERMISSIONS_CREATED)
710
751
  Aspera.assert_type(types, Array)
711
752
  Aspera.assert(!types.empty?)
712
753
  event_creation = {
713
754
  'types' => types,
714
- 'node_id' => app_info[:node_info]['id'],
715
- 'workspace_id' => app_info[:workspace_id],
755
+ 'node_id' => app_info.node_info['id'],
756
+ 'workspace_id' => app_info.workspace_id,
716
757
  'data' => event_data
717
758
  }
718
759
  # (optional). The name of the folder to be displayed to the destination user.
719
760
  # Use it if its value is different from the "share_as" field.
720
- event_creation['link_name'] = app_info[:opt_link_name] unless app_info[:opt_link_name].nil?
761
+ event_creation['link_name'] = app_info.opt_link_name unless app_info.opt_link_name.nil?
721
762
  create('events', event_creation, query: {admin: true})
722
763
  end
723
764
  end