aspera-cli 4.25.5 → 4.26.0

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 (42) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +87 -54
  4. data/CONTRIBUTING.md +11 -2
  5. data/lib/aspera/api/aoc.rb +118 -78
  6. data/lib/aspera/api/node.rb +126 -61
  7. data/lib/aspera/ascp/installation.rb +117 -54
  8. data/lib/aspera/cli/extended_value.rb +1 -0
  9. data/lib/aspera/cli/formatter.rb +47 -40
  10. data/lib/aspera/cli/manager.rb +30 -4
  11. data/lib/aspera/cli/plugins/aoc.rb +214 -136
  12. data/lib/aspera/cli/plugins/ats.rb +3 -3
  13. data/lib/aspera/cli/plugins/base.rb +17 -42
  14. data/lib/aspera/cli/plugins/config.rb +5 -3
  15. data/lib/aspera/cli/plugins/console.rb +3 -3
  16. data/lib/aspera/cli/plugins/faspex.rb +5 -5
  17. data/lib/aspera/cli/plugins/faspex5.rb +20 -18
  18. data/lib/aspera/cli/plugins/node.rb +66 -70
  19. data/lib/aspera/cli/plugins/oauth.rb +5 -12
  20. data/lib/aspera/cli/plugins/orchestrator.rb +13 -13
  21. data/lib/aspera/cli/plugins/preview.rb +116 -80
  22. data/lib/aspera/cli/plugins/server.rb +3 -11
  23. data/lib/aspera/cli/plugins/shares.rb +7 -7
  24. data/lib/aspera/cli/version.rb +1 -1
  25. data/lib/aspera/dot_container.rb +7 -3
  26. data/lib/aspera/environment.rb +30 -19
  27. data/lib/aspera/keychain/macos_security.rb +1 -1
  28. data/lib/aspera/log.rb +1 -1
  29. data/lib/aspera/preview/file_types.rb +1 -1
  30. data/lib/aspera/preview/generator.rb +146 -91
  31. data/lib/aspera/preview/options.rb +4 -1
  32. data/lib/aspera/preview/terminal.rb +50 -20
  33. data/lib/aspera/preview/utils.rb +76 -34
  34. data/lib/aspera/products/transferd.rb +1 -1
  35. data/lib/aspera/rest.rb +32 -0
  36. data/lib/aspera/rest_list.rb +23 -16
  37. data/lib/aspera/secret_hider.rb +3 -1
  38. data/lib/aspera/sync/operations.rb +1 -1
  39. data/lib/aspera/uri_reader.rb +17 -2
  40. data.tar.gz.sig +0 -0
  41. metadata +5 -5
  42. metadata.gz.sig +0 -0
@@ -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,
@@ -350,34 +397,38 @@ module Aspera
350
397
  return @cache_user_info
351
398
  end
352
399
 
353
- # Cached workspace information
354
- def workspace
400
+ def default_workspace?
401
+ @ws_ids[:name].eql?(DEFAULT_WORKSPACE)
402
+ end
403
+
404
+ # Cached workspace information.
405
+ # Always with `:name`.
406
+ # If no workspace, then no `:id`
407
+ # @return [Hash{Symbol=>String}] Workspace info.
408
+ def workspace_info
355
409
  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
410
+ if !public_link.nil?
411
+ Log.log.debug('Using workspace of public link')
412
+ @ws_ids[:id] = public_link['data']['workspace_id']
413
+ elsif !private_link.nil?
414
+ Log.log.debug('Using workspace of private link')
415
+ @ws_ids[:id] = private_link[:workspace_id]
416
+ elsif @ws_ids[:id]
417
+ Log.log.debug("Using workspace id: #{@ws_ids[:id]}")
418
+ elsif default_workspace?
419
+ if current_user_info['default_workspace_id'].nil?
420
+ @ws_ids[:name] = nil
421
+ Log.log.warn('No default workspace')
370
422
  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
423
+ Log.log.debug('Using default workspace'.green)
424
+ @ws_ids[:id] = current_user_info['default_workspace_id']
380
425
  end
426
+ elsif !@ws_ids[:name].nil?
427
+ @workspace_info = lookup_with_q('workspaces', value: @ws_ids[:name])
428
+ end
429
+ @workspace_info = read("workspaces/#{@ws_ids[:id]}") unless @ws_ids[:id].nil?
430
+ @workspace_info ||= {name: 'Shared (no workspace)'}
431
+ @workspace_info = @workspace_info.slice('name', 'id', 'home_node_id', 'home_file_id').symbolize_keys
381
432
  Log.dump(:workspace_info, @workspace_info)
382
433
  @workspace_info
383
434
  end
@@ -397,10 +448,10 @@ module Aspera
397
448
  node_id: private_link[:node_id],
398
449
  file_id: private_link[:file_id]
399
450
  }
400
- elsif workspace[:home_node_id] && workspace[:home_file_id]
451
+ elsif workspace_info[:home_node_id] && workspace_info[:home_file_id]
401
452
  {
402
- node_id: workspace[:home_node_id],
403
- file_id: workspace[:home_file_id]
453
+ node_id: workspace_info[:home_node_id],
454
+ file_id: workspace_info[:home_file_id]
404
455
  }
405
456
  else
406
457
  # not part of any workspace, but has some folder shared
@@ -426,18 +477,7 @@ module Aspera
426
477
  Aspera.assert_type(node_id, String)
427
478
  node_info = read("nodes/#{node_id}")
428
479
  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
480
+ app_info = AppInfo.new(self, package_info, node_info, workspace_id, workspace_name)
441
481
  node_params = {base_url: node_info['url']}
442
482
  ak_secret = @secret_finder&.lookup_secret(url: node_info['url'], username: node_info['access_key'])
443
483
  # If secret is available, or no scope, use basic auth
@@ -514,7 +554,7 @@ module Aspera
514
554
  # email: user, else dropbox
515
555
  entity_type = short_recipient_info.include?('@') ? 'contacts' : 'dropboxes'
516
556
  begin
517
- full_recipient_info = lookup_by_name(entity_type, short_recipient_info, query: {'workspace_id' => ws_id})
557
+ full_recipient_info = lookup_with_q(entity_type, value: short_recipient_info, query: {'workspace_id' => ws_id})
518
558
  rescue EntityNotFound
519
559
  # dropboxes cannot be created on the fly
520
560
  Aspera.assert_values(entity_type, 'contacts', type: Error){"No such shared inbox in workspace #{ws_id}"}
@@ -609,13 +649,13 @@ module Aspera
609
649
  transfer_spec.deep_merge!({
610
650
  'tags' => {
611
651
  Transfer::Spec::TAG_RESERVED => {
612
- 'app' => app_info[:app],
613
- 'usage_id' => "aspera.files.workspace.#{app_info[:workspace_id]}", # activity tracking
652
+ 'app' => app_info.app,
653
+ 'usage_id' => "aspera.files.workspace.#{app_info.workspace_id}", # activity tracking
614
654
  '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]
655
+ 'node_id' => app_info.node_info['id'],
656
+ 'files_transfer_action' => "#{transfer_type}_#{app_info.app.gsub(/s$/, '')}",
657
+ 'workspace_name' => app_info.workspace_name, # activity tracking
658
+ 'workspace_id' => app_info.workspace_id
619
659
  }
620
660
  }
621
661
  }
@@ -623,30 +663,30 @@ module Aspera
623
663
  # Console cookie
624
664
  ################
625
665
  # 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)}
666
+ cookie_elements = [app_info.app, current_user_info['name'] || 'public link', current_user_info['email'] || 'none'].map{ |e| Base64.strict_encode64(e)}
627
667
  cookie_elements.unshift(COOKIE_PREFIX_CONSOLE_AOC)
628
668
  transfer_spec['cookie'] = cookie_elements.join(':')
629
669
  # Application tags
630
670
  ##################
631
- case app_info[:app]
632
- when FILES_APP
671
+ case app_info.app
672
+ when AppType::FILES
633
673
  file_id = transfer_spec['tags'][Transfer::Spec::TAG_RESERVED]['node']['file_id']
634
674
  transfer_spec.deep_merge!({
635
675
  'tags' => {
636
676
  Transfer::Spec::TAG_RESERVED => {
637
677
  'files' => {
638
- 'parentCwd' => "#{app_info[:node_info]['id']}:#{file_id}"
678
+ 'parentCwd' => "#{app_info.node_info['id']}:#{file_id}"
639
679
  }
640
680
  }
641
681
  }
642
682
  }) unless transfer_spec.key?('remote_access_key')
643
- when PACKAGES_APP
683
+ when AppType::PACKAGES
644
684
  transfer_spec.deep_merge!({
645
685
  'tags' => {
646
686
  Transfer::Spec::TAG_RESERVED => {
647
687
  'files' => {
648
- 'package_id' => app_info[:package_id],
649
- 'package_name' => app_info[:package_name],
688
+ 'package_id' => app_info.package['id'],
689
+ 'package_name' => app_info.package['name'],
650
690
  'package_operation' => transfer_type
651
691
  }
652
692
  }
@@ -658,21 +698,21 @@ module Aspera
658
698
  # Callback from Plugins::Node
659
699
  # add application specific tags to permissions creation
660
700
  # @param perm_data [Hash] parameters for creating permissions
661
- # @param app_info [Hash] application information
701
+ # @param app_info [AppInfo] application information
662
702
  def permissions_set_create_params(perm_data:, app_info:)
663
703
  defaults = {
664
704
  'tags' => {
665
705
  Transfer::Spec::TAG_RESERVED => {
666
706
  'files' => {
667
707
  'workspace' => {
668
- 'id' => app_info[:workspace_id],
669
- 'workspace_name' => app_info[:workspace_name],
708
+ 'id' => app_info.workspace_id,
709
+ 'workspace_name' => app_info.workspace_name,
670
710
  'user_name' => current_user_info['name'],
671
711
  'shared_by_user_id' => current_user_info['id'],
672
712
  'shared_by_name' => current_user_info['name'],
673
713
  'shared_by_email' => current_user_info['email'],
674
- 'access_key' => app_info[:node_info]['access_key'],
675
- 'node' => app_info[:node_info]['name']
714
+ 'access_key' => app_info.node_info['access_key'],
715
+ 'node' => app_info.node_info['name']
676
716
  }
677
717
  }
678
718
  }
@@ -685,10 +725,10 @@ module Aspera
685
725
  when NilClass
686
726
  when ''
687
727
  # workspace shared folder
688
- perm_data.merge!(self.class.workspace_access(app_info[:workspace_id]))
728
+ perm_data.merge!(self.class.workspace_access(app_info.workspace_id))
689
729
  tag_workspace['shared_with_name'] = perm_data['access_id']
690
730
  else
691
- entity_info = lookup_by_name('contacts', shared_with, query: {'current_workspace_id' => app_info[:workspace_id]})
731
+ entity_info = lookup_with_q('contacts', value: shared_with, query: {'current_workspace_id' => app_info.workspace_id})
692
732
  perm_data['access_type'] = entity_info['source_type']
693
733
  perm_data['access_id'] = entity_info['source_id']
694
734
  tag_workspace['shared_with_name'] = entity_info['email'] # TODO: check that ???
@@ -698,26 +738,26 @@ module Aspera
698
738
  perm_data.delete('as')
699
739
  end
700
740
  # optional
701
- app_info[:opt_link_name] = perm_data.delete('link_name')
741
+ app_info.opt_link_name = perm_data.delete('link_name')
702
742
  end
703
743
 
704
744
  # Callback from Plugins::Node
705
745
  # send shared folder event to AoC
706
746
  # @param event_data [Hash] response from permission creation
707
- # @param app_info [Hash] hash with app info
747
+ # @param app_info [AppInfo] hash with app info
708
748
  # @param types [Array] event types
709
749
  def permissions_send_event(event_data:, app_info:, types: PERMISSIONS_CREATED)
710
750
  Aspera.assert_type(types, Array)
711
751
  Aspera.assert(!types.empty?)
712
752
  event_creation = {
713
753
  'types' => types,
714
- 'node_id' => app_info[:node_info]['id'],
715
- 'workspace_id' => app_info[:workspace_id],
754
+ 'node_id' => app_info.node_info['id'],
755
+ 'workspace_id' => app_info.workspace_id,
716
756
  'data' => event_data
717
757
  }
718
758
  # (optional). The name of the folder to be displayed to the destination user.
719
759
  # 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?
760
+ event_creation['link_name'] = app_info.opt_link_name unless app_info.opt_link_name.nil?
721
761
  create('events', event_creation, query: {admin: true})
722
762
  end
723
763
  end