aspera-cli 4.14.0 → 4.16.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 (104) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/BUGS.md +29 -3
  4. data/CHANGELOG.md +300 -185
  5. data/CONTRIBUTING.md +74 -23
  6. data/README.md +2346 -1619
  7. data/bin/ascli +16 -25
  8. data/bin/asession +15 -15
  9. data/examples/dascli +2 -2
  10. data/examples/proxy.pac +1 -1
  11. data/lib/aspera/aoc.rb +216 -150
  12. data/lib/aspera/ascmd.rb +25 -18
  13. data/lib/aspera/assert.rb +45 -0
  14. data/lib/aspera/cli/basic_auth_plugin.rb +9 -6
  15. data/lib/aspera/cli/error.rb +17 -0
  16. data/lib/aspera/cli/extended_value.rb +51 -16
  17. data/lib/aspera/cli/formatter.rb +276 -174
  18. data/lib/aspera/cli/hints.rb +81 -0
  19. data/lib/aspera/cli/main.rb +114 -147
  20. data/lib/aspera/cli/manager.rb +181 -136
  21. data/lib/aspera/cli/plugin.rb +82 -64
  22. data/lib/aspera/cli/plugins/alee.rb +0 -1
  23. data/lib/aspera/cli/plugins/aoc.rb +327 -331
  24. data/lib/aspera/cli/plugins/ats.rb +12 -8
  25. data/lib/aspera/cli/plugins/bss.rb +2 -2
  26. data/lib/aspera/cli/plugins/config.rb +575 -439
  27. data/lib/aspera/cli/plugins/console.rb +40 -0
  28. data/lib/aspera/cli/plugins/cos.rb +4 -5
  29. data/lib/aspera/cli/plugins/faspex.rb +111 -92
  30. data/lib/aspera/cli/plugins/faspex5.rb +245 -182
  31. data/lib/aspera/cli/plugins/node.rb +239 -160
  32. data/lib/aspera/cli/plugins/orchestrator.rb +56 -19
  33. data/lib/aspera/cli/plugins/preview.rb +54 -38
  34. data/lib/aspera/cli/plugins/server.rb +63 -20
  35. data/lib/aspera/cli/plugins/shares.rb +64 -38
  36. data/lib/aspera/cli/sync_actions.rb +68 -0
  37. data/lib/aspera/cli/transfer_agent.rb +64 -67
  38. data/lib/aspera/cli/transfer_progress.rb +73 -0
  39. data/lib/aspera/cli/version.rb +1 -1
  40. data/lib/aspera/colors.rb +3 -1
  41. data/lib/aspera/command_line_builder.rb +27 -22
  42. data/lib/aspera/cos_node.rb +6 -4
  43. data/lib/aspera/coverage.rb +22 -0
  44. data/lib/aspera/data_repository.rb +33 -2
  45. data/lib/aspera/environment.rb +21 -8
  46. data/lib/aspera/fasp/agent_alpha.rb +116 -0
  47. data/lib/aspera/fasp/agent_base.rb +40 -76
  48. data/lib/aspera/fasp/agent_connect.rb +21 -22
  49. data/lib/aspera/fasp/agent_direct.rb +169 -179
  50. data/lib/aspera/fasp/agent_httpgw.rb +200 -195
  51. data/lib/aspera/fasp/agent_node.rb +43 -35
  52. data/lib/aspera/fasp/agent_trsdk.rb +124 -41
  53. data/lib/aspera/fasp/error_info.rb +2 -2
  54. data/lib/aspera/fasp/faux_file.rb +52 -0
  55. data/lib/aspera/fasp/installation.rb +89 -191
  56. data/lib/aspera/fasp/management.rb +249 -0
  57. data/lib/aspera/fasp/parameters.rb +86 -47
  58. data/lib/aspera/fasp/parameters.yaml +75 -8
  59. data/lib/aspera/fasp/products.rb +162 -0
  60. data/lib/aspera/fasp/resume_policy.rb +7 -5
  61. data/lib/aspera/fasp/sync.rb +273 -0
  62. data/lib/aspera/fasp/transfer_spec.rb +10 -8
  63. data/lib/aspera/fasp/uri.rb +6 -6
  64. data/lib/aspera/faspex_gw.rb +11 -8
  65. data/lib/aspera/faspex_postproc.rb +8 -7
  66. data/lib/aspera/hash_ext.rb +2 -2
  67. data/lib/aspera/id_generator.rb +3 -1
  68. data/lib/aspera/json_rpc.rb +51 -0
  69. data/lib/aspera/keychain/encrypted_hash.rb +46 -11
  70. data/lib/aspera/keychain/macos_security.rb +15 -13
  71. data/lib/aspera/line_logger.rb +23 -0
  72. data/lib/aspera/log.rb +61 -19
  73. data/lib/aspera/nagios.rb +7 -2
  74. data/lib/aspera/node.rb +105 -21
  75. data/lib/aspera/node_simulator.rb +214 -0
  76. data/lib/aspera/oauth.rb +57 -36
  77. data/lib/aspera/open_application.rb +4 -4
  78. data/lib/aspera/persistency_action_once.rb +13 -14
  79. data/lib/aspera/persistency_folder.rb +5 -4
  80. data/lib/aspera/preview/file_types.rb +56 -268
  81. data/lib/aspera/preview/generator.rb +28 -39
  82. data/lib/aspera/preview/options.rb +2 -0
  83. data/lib/aspera/preview/terminal.rb +36 -16
  84. data/lib/aspera/preview/utils.rb +23 -29
  85. data/lib/aspera/proxy_auto_config.rb +6 -3
  86. data/lib/aspera/rest.rb +127 -80
  87. data/lib/aspera/rest_call_error.rb +1 -1
  88. data/lib/aspera/rest_error_analyzer.rb +16 -14
  89. data/lib/aspera/rest_errors_aspera.rb +39 -34
  90. data/lib/aspera/secret_hider.rb +18 -17
  91. data/lib/aspera/ssh.rb +10 -5
  92. data/lib/aspera/temp_file_manager.rb +11 -4
  93. data/lib/aspera/web_auth.rb +10 -7
  94. data/lib/aspera/web_server_simple.rb +11 -5
  95. data.tar.gz.sig +0 -0
  96. metadata +108 -39
  97. metadata.gz.sig +0 -0
  98. data/lib/aspera/cli/listener/line_dump.rb +0 -19
  99. data/lib/aspera/cli/listener/logger.rb +0 -22
  100. data/lib/aspera/cli/listener/progress.rb +0 -50
  101. data/lib/aspera/cli/listener/progress_multi.rb +0 -84
  102. data/lib/aspera/cli/plugins/sync.rb +0 -44
  103. data/lib/aspera/fasp/listener.rb +0 -13
  104. data/lib/aspera/sync.rb +0 -213
@@ -10,6 +10,7 @@ require 'aspera/aoc'
10
10
  require 'aspera/node'
11
11
  require 'aspera/persistency_action_once'
12
12
  require 'aspera/id_generator'
13
+ require 'aspera/assert'
13
14
  require 'securerandom'
14
15
  require 'date'
15
16
 
@@ -17,18 +18,130 @@ module Aspera
17
18
  module Cli
18
19
  module Plugins
19
20
  class Aoc < Aspera::Cli::BasicAuthPlugin
21
+ AOC_PATH_API_CLIENTS = 'admin/api-clients'
22
+ # default redirect for AoC web auth
23
+ DEFAULT_REDIRECT = 'http://localhost:12345'
24
+ private_constant :AOC_PATH_API_CLIENTS, :DEFAULT_REDIRECT
20
25
  class << self
26
+ def application_name
27
+ 'Aspera on Cloud'
28
+ end
29
+
21
30
  def detect(base_url)
22
- api = Rest.new({base_url: base_url})
31
+ # no protocol ?
32
+ base_url = "https://#{base_url}" unless base_url.match?(%r{^[a-z]{1,6}://})
33
+ # only org provided ?
34
+ base_url = "#{base_url}.#{Aspera::AoC::PROD_DOMAIN}" unless base_url.include?('.')
35
+ # AoC is only https
36
+ return nil unless base_url.start_with?('https://')
37
+ result = Rest.new({base_url: base_url, redirect_max: 10}).read('')
38
+ # Any AoC is on this domain
39
+ return nil unless result[:http].uri.host.end_with?(Aspera::AoC::PROD_DOMAIN)
40
+ Log.log.debug{'AoC Main page: #{result[:http].body.include?(Aspera::AoC::PRODUCT_NAME)}'}
41
+ base_url = result[:http].uri.to_s if result[:http].uri.path.include?('/public')
23
42
  # either in standard domain, or product name in page
24
- if URI.parse(base_url).host.end_with?(Aspera::AoC::PROD_DOMAIN) ||
25
- api.call({operation: 'GET', redirect_max: 1, headers: {'Accept' => 'text/html'}})[:http].body.include?(Aspera::AoC::PRODUCT_NAME)
43
+ return {
44
+ version: 'SaaS',
45
+ url: base_url
46
+ }
47
+ end
48
+
49
+ def private_key_required?(url)
50
+ # pub link do not need private key
51
+ return AoC.link_info(url)[:token].nil?
52
+ end
53
+
54
+ # @param [Hash] env : options, formatter
55
+ # @param [Hash] params : plugin_sym, instance_url
56
+ # @return [Hash] :preset_value, :test_args
57
+ def wizard(object:, private_key_path: nil, pub_key_pem: nil)
58
+ # set vars to look like object
59
+ options = object.options
60
+ formatter = object.formatter
61
+ options.declare(:use_generic_client, 'Wizard: AoC: use global or org specific jwt client id', values: :bool, default: true)
62
+ options.parse_options!
63
+ instance_url = options.get_option(:url, mandatory: true)
64
+ pub_link_info = AoC.link_info(instance_url)
65
+ if !pub_link_info[:token].nil?
66
+ pub_api = Rest.new({base_url: "https://#{URI.parse(pub_link_info[:url]).host}/api/v1"})
67
+ pub_info = pub_api.read('env/url_token_check', {token: pub_link_info[:token]})[:data]
68
+ preset_value = {
69
+ link: instance_url
70
+ }
71
+ preset_value[:password] = options.get_option(:password, mandatory: true) if pub_info['password_protected']
26
72
  return {
27
- version: 'SaaS',
28
- name: 'Aspera on Cloud'
73
+ preset_value: preset_value,
74
+ test_args: 'organization'
29
75
  }
30
76
  end
31
- return nil
77
+ # make username mandatory for jwt, this triggers interactive input
78
+ wiz_username = options.get_option(:username, mandatory: true)
79
+ raise "Username shall be an email in AoC: #{wiz_username}" if !(wiz_username =~ /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i)
80
+ # Set the pub key and jwt tag in the user's profile automatically
81
+ auto_set_pub_key = false
82
+ auto_set_jwt = false
83
+ # use browser authentication to bootstrap
84
+ use_browser_authentication = false
85
+ if options.get_option(:use_generic_client)
86
+ formatter.display_status('Using global client_id.')
87
+ formatter.display_status('Please Login to your Aspera on Cloud instance.')
88
+ formatter.display_status('Navigate to: 👤 → Account Settings → Profile → Public Key')
89
+ formatter.display_status('Check or update the value to:'.red.blink)
90
+ formatter.display_status(pub_key_pem)
91
+ if !options.get_option(:test_mode)
92
+ formatter.display_status('Once updated or validated, press enter.')
93
+ OpenApplication.instance.uri(instance_url)
94
+ $stdin.gets
95
+ end
96
+ else
97
+ formatter.display_status('Using organization specific client_id.')
98
+ if options.get_option(:client_id).nil? || options.get_option(:client_secret).nil?
99
+ formatter.display_status('Please login to your Aspera on Cloud instance.'.red)
100
+ formatter.display_status('Navigate to: 𓃑 → Admin → Integrations → API Clients')
101
+ formatter.display_status('Check or create in integration:')
102
+ formatter.display_status("- name: #{@info[:name]}")
103
+ formatter.display_status("- redirect uri: #{DEFAULT_REDIRECT}")
104
+ formatter.display_status('- origin: localhost')
105
+ formatter.display_status('Use the generated client id and secret in the following prompts.'.red)
106
+ end
107
+ OpenApplication.instance.uri("#{instance_url}/#{AOC_PATH_API_CLIENTS}")
108
+ options.get_option(:client_id, mandatory: true)
109
+ options.get_option(:client_secret, mandatory: true)
110
+ use_browser_authentication = true
111
+ end
112
+ if use_browser_authentication
113
+ formatter.display_status('We will use web authentication to bootstrap.')
114
+ auto_set_pub_key = true
115
+ auto_set_jwt = true
116
+ aoc_api.oauth.generic_parameters[:grant_method] = :web
117
+ aoc_api.oauth.generic_parameters[:scope] = AoC::SCOPE_FILES_ADMIN
118
+ aoc_api.oauth.specific_parameters[:redirect_uri] = DEFAULT_REDIRECT
119
+ end
120
+ myself = object.aoc_api.read('self')[:data]
121
+ if auto_set_pub_key
122
+ assert(myself['public_key'].empty?, exception_class: Cli::Error){'Public key is already set in profile (use --override=yes)'} unless option_override
123
+ formatter.display_status('Updating profile with the public key.')
124
+ aoc_api.update("users/#{myself['id']}", {'public_key' => pub_key_pem})
125
+ end
126
+ if auto_set_jwt
127
+ formatter.display_status('Enabling JWT for client')
128
+ aoc_api.update("clients/#{options.get_option(:client_id)}", {'jwt_grant_enabled' => true, 'explicit_authorization_required' => false})
129
+ end
130
+ preset_result = {
131
+ url: instance_url,
132
+ username: myself['email'],
133
+ auth: :jwt.to_s,
134
+ private_key: "@file:#{private_key_path}"
135
+ }
136
+ # set only if non nil
137
+ %i[client_id client_secret].each do |s|
138
+ o = options.get_option(s)
139
+ preset_result[s.to_s] = o unless o.nil?
140
+ end
141
+ return {
142
+ preset_value: preset_result,
143
+ test_args: 'user profile show'
144
+ }
32
145
  end
33
146
  end
34
147
  # special value for package id
@@ -53,9 +166,11 @@ module Aspera
53
166
  client_registration_token
54
167
  client_access_key
55
168
  kms_profile].freeze
56
- # TODO: remove this and use %name: instead
57
- ENTITY_NAME_SPECIFIER = 'name'
58
- PACKAGE_QUERY_DEFAULT = {'archived' => false, 'exclude_dropbox_packages' => true, 'has_content' => true, 'received' => true}.freeze
169
+ PACKAGE_RECEIVED_BASE_QUERY = {
170
+ 'archived' => false,
171
+ 'has_content' => true,
172
+ 'received' => true,
173
+ 'completed' => true}.freeze
59
174
 
60
175
  def initialize(env)
61
176
  super(env)
@@ -63,135 +178,60 @@ module Aspera
63
178
  @cache_home_node_file = nil
64
179
  @cache_api_aoc = nil
65
180
  options.declare(:auth, 'OAuth type of authentication', values: Oauth::STD_AUTH_TYPES, default: :jwt)
66
- options.declare(:operation, 'Client operation for transfers', values: %i[push pull], default: :push)
67
181
  options.declare(:client_id, 'OAuth API client identifier')
68
182
  options.declare(:client_secret, 'OAuth API client secret')
183
+ options.declare(:scope, 'OAuth scope for AoC API calls', default: AoC::SCOPE_FILES_USER)
69
184
  options.declare(:redirect_uri, 'OAuth API client redirect URI')
70
185
  options.declare(:private_key, 'OAuth JWT RSA private key PEM value (prefix file path with @file:)')
71
- options.declare(:scope, 'OAuth scope for AoC API calls', default: AoC::SCOPE_FILES_USER)
72
186
  options.declare(:passphrase, 'RSA private key passphrase')
73
- options.declare(:workspace, 'Name of workspace', default: :default)
74
- # TODO: remove this and use %name: instead
75
- options.declare(:name, "Resource name (prefer to use keyword #{ENTITY_NAME_SPECIFIER})")
76
- options.declare(:link, 'Public link to shared resource')
187
+ options.declare(:workspace, 'Name of workspace', types: [String, NilClass], default: Aspera::AoC::DEFAULT_WORKSPACE)
77
188
  options.declare(:new_user_option, 'New user creation option for unknown package recipients')
78
- options.declare(:from_folder, 'Source folder for Folder-to-Folder transfer')
79
189
  options.declare(:validate_metadata, 'Validate shared inbox metadata', values: :bool, default: true)
80
190
  options.parse_options!
81
- # add node plugin options (TODO: check needed ? if yes, tell why)
82
- Node.new(env.merge({man_only: true, skip_basic_auth_options: true}))
191
+ # add node plugin options (for manual)
192
+ Node.declare_options(options)
83
193
  end
84
194
 
85
- # build list of options for AoC API, based on options of CLI
86
- def aoc_params(subpath)
87
- # copy command line options to args
88
- return Aspera::AoC::OPTIONS_NEW.each_with_object({subpath: subpath}){|i, m|m[i] = options.get_option(i)}
89
- end
90
-
91
- def aoc_api
92
- if @cache_api_aoc.nil?
93
- @cache_api_aoc = AoC.new(aoc_params(AoC::API_V1))
94
- # add keychain for access key secrets
95
- @cache_api_aoc.secret_finder = @agents[:config]
96
- end
97
- return @cache_api_aoc
98
- end
195
+ OPTIONS_NEW = %i[url auth client_id client_secret scope redirect_uri private_key passphrase username password workspace].freeze
99
196
 
100
- # @return [Hash] current workspace information,
101
- def current_workspace_info
102
- return @cache_workspace_info unless @cache_workspace_info.nil?
103
- default_workspace_id = if aoc_api.url_token_data.nil?
104
- aoc_api.current_user_info['default_workspace_id']
105
- else
106
- aoc_api.url_token_data['data']['workspace_id']
197
+ def api_from_options(new_base_path)
198
+ create_values = {subpath: new_base_path, secret_finder: @agents[:config]}
199
+ # create an API object with the same options, but with a different subpath
200
+ return Aspera::AoC.new(**OPTIONS_NEW.each_with_object(create_values) { |i, m|m[i] = options.get_option(i) unless options.get_option(i).nil?})
201
+ rescue ArgumentError => e
202
+ if (m = e.message.match(/missing keyword: :(.*)$/))
203
+ raise Cli::Error, "Missing option: #{m[1]}"
107
204
  end
108
-
109
- ws_name = options.get_option(:workspace)
110
- ws_id =
111
- case ws_name
112
- when :default
113
- Log.log.debug('Using default workspace'.green)
114
- raise CliError, 'No default workspace defined for user, please specify workspace' if default_workspace_id.nil?
115
- default_workspace_id
116
- when String then aoc_api.lookup_by_name('workspaces', ws_name)['id']
117
- when NilClass then nil
118
- else raise CliError, 'unexpected value type for workspace'
119
- end
120
- @cache_workspace_info =
121
- begin
122
- aoc_api.read("workspaces/#{ws_id}")[:data]
123
- rescue Aspera::RestCallError => e
124
- Log.log.debug(e.message)
125
- { 'id' => :undefined, 'name' => :undefined }
126
- end
127
- Log.dump(:current_workspace_info, @cache_workspace_info)
128
- # display workspace
129
- default_flag = @cache_workspace_info['id'] == default_workspace_id ? ' (default)' : ''
130
- formatter.display_status("Current Workspace: #{@cache_workspace_info['name'].to_s.red}#{default_flag}")
131
- return @cache_workspace_info
205
+ raise
132
206
  end
133
207
 
134
- # @return [Hash] with :node_id and :file_id
135
- def home_info
136
- return @cache_home_node_file unless @cache_home_node_file.nil?
137
- if !aoc_api.url_token_data.nil?
138
- assert_public_link_types(['view_shared_file'])
139
- home_node_id = aoc_api.url_token_data['data']['node_id']
140
- home_file_id = aoc_api.url_token_data['data']['file_id']
141
- end
142
- home_node_id ||= current_workspace_info['home_node_id'] || current_workspace_info['node_id']
143
- home_file_id ||= current_workspace_info['home_file_id']
144
- if home_node_id.to_s.empty?
145
- # not part of any workspace, but has some folder shared
146
- user_info = aoc_api.current_user_info(exception: true)
147
- home_node_id = user_info['read_only_home_node_id']
148
- home_file_id = user_info['read_only_home_file_id']
149
- end
150
-
151
- raise "Cannot get user's home node id, check your default workspace or specify one" if home_node_id.to_s.empty?
152
- @cache_home_node_file = {
153
- node_id: home_node_id,
154
- file_id: home_file_id
155
- }
156
- return @cache_home_node_file
208
+ def aoc_api
209
+ @cache_api_aoc = api_from_options(AoC::API_V1) if @cache_api_aoc.nil?
210
+ return @cache_api_aoc
157
211
  end
158
212
 
159
213
  # get identifier or name from command line
160
214
  # @return identifier
161
215
  def get_resource_id_from_args(resource_class_path)
162
- l_res_id = options.get_option(:id)
163
- l_res_name = options.get_option(:name)
164
- raise 'Provide either option id or name, not both' unless l_res_id.nil? || l_res_name.nil?
165
- # try to find item by name (single partial match or exact match)
166
- l_res_id = aoc_api.lookup_by_name(resource_class_path, l_res_name)['id'] unless l_res_name.nil?
167
- # if no name or id option, taken on command line (after command)
168
- if l_res_id.nil?
169
- l_res_id = options.get_next_argument('identifier')
170
- l_res_id = aoc_api.lookup_by_name(resource_class_path, options.get_next_argument('identifier'))['id'] if l_res_id.eql?(ENTITY_NAME_SPECIFIER)
216
+ return instance_identifier do |field, value|
217
+ assert(field.eql?('name'), exception_class: Cli::BadArgument){'only selection by name is supported'}
218
+ aoc_api.lookup_by_name(resource_class_path, value)['id']
171
219
  end
172
- return l_res_id
173
220
  end
174
221
 
175
222
  def get_resource_path_from_args(resource_class_path)
176
223
  return "#{resource_class_path}/#{get_resource_id_from_args(resource_class_path)}"
177
224
  end
178
225
 
179
- def assert_public_link_types(expected)
180
- raise CliBadArgument, "public link type is #{aoc_api.url_token_data['purpose']} but action requires one of #{expected.join(',')}" \
181
- unless expected.include?(aoc_api.url_token_data['purpose'])
182
- end
183
-
184
- # Call aoc_api.read with same parameters.
185
- # Use paging if necessary to get all results
186
- # @return [Hash] {list: , total: }
187
- def read_with_paging(resource_class_path, base_query)
188
- raise 'Query must be Hash' unless base_query.is_a?(Hash)
226
+ # Call block with same query using paging and response information
227
+ # @return [Hash] {data: , total: }
228
+ def api_call_paging(base_query={})
229
+ assert_type(base_query, Hash){'query'}
230
+ assert(block_given?)
189
231
  # set default large page if user does not specify own parameters. AoC Caps to 1000 anyway
190
232
  base_query['per_page'] = 1000 unless base_query.key?('per_page')
191
- max_items = base_query[MAX_ITEMS]
192
- base_query.delete(MAX_ITEMS)
193
- max_pages = base_query[MAX_PAGES]
194
- base_query.delete(MAX_PAGES)
233
+ max_items = base_query.delete(MAX_ITEMS)
234
+ max_pages = base_query.delete(MAX_PAGES)
195
235
  item_list = []
196
236
  total_count = nil
197
237
  current_page = base_query['page']
@@ -200,7 +240,7 @@ module Aspera
200
240
  loop do
201
241
  query = base_query.clone
202
242
  query['page'] = current_page
203
- result = aoc_api.read(resource_class_path, query)
243
+ result = yield(query)
204
244
  total_count = result[:http]['X-Total-Count']
205
245
  page_count += 1
206
246
  current_page += 1
@@ -208,10 +248,40 @@ module Aspera
208
248
  break if add_items.empty?
209
249
  # append new items to full list
210
250
  item_list += add_items
211
- break if !max_pages.nil? && page_count > max_pages
212
- break if !max_items.nil? && item_list.count > max_items
251
+ break if !max_items.nil? && item_list.count >= max_items
252
+ break if !max_pages.nil? && page_count >= max_pages
213
253
  end
214
- return {list: item_list, total: total_count}
254
+ item_list = item_list[0..max_items - 1] if !max_items.nil? && item_list.count > max_items
255
+ return {data: item_list, total: total_count}
256
+ end
257
+
258
+ # read using the query and paging
259
+ # @return [Hash] {data: , total: }
260
+ def api_read_all(resource_class_path, base_query={})
261
+ return api_call_paging(base_query) do |query|
262
+ aoc_api.read(resource_class_path, query)
263
+ end
264
+ end
265
+
266
+ # list all entities, given additional, default and user's queries
267
+ def result_list(resource_class_path, fields: nil, base_query: {}, default_query: {})
268
+ assert_type(base_query, Hash)
269
+ assert_type(default_query, Hash)
270
+ user_query = query_read_delete(default: default_query)
271
+ # caller may add specific modifications or checks
272
+ yield(user_query) if block_given?
273
+ return {type: :object_list, fields: fields}.merge(api_read_all(resource_class_path, base_query.merge(user_query)))
274
+ end
275
+
276
+ def resolve_dropbox_name_default_ws_id(query)
277
+ if query.key?('dropbox_name')
278
+ # convenience: specify name instead of id
279
+ raise 'not both dropbox_name and dropbox_id' if query.key?('dropbox_id')
280
+ query['dropbox_id'] = aoc_api.lookup_by_name('dropboxes', query['dropbox_name'])['id']
281
+ query.delete('dropbox_name')
282
+ end
283
+ query['workspace_id'] ||= aoc_api.context[:workspace_id] unless aoc_api.context[:workspace_id].eql?(:undefined)
284
+ query['exclude_dropbox_packages'] = true unless query.key?('dropbox_id')
215
285
  end
216
286
 
217
287
  NODE4_EXT_COMMANDS = %i[transfer].concat(Node::COMMANDS_GEN4).freeze
@@ -221,33 +291,32 @@ module Aspera
221
291
  # @param scope [String] node scope, or nil (admin)
222
292
  def execute_nodegen4_command(command_repo, node_id, file_id: nil, scope: nil)
223
293
  top_node_api = aoc_api.node_api_from(
224
- node_id: node_id,
225
- workspace_id: current_workspace_info['id'],
226
- workspace_name: current_workspace_info['name'],
227
- scope: scope
294
+ node_id: node_id,
295
+ workspace_id: aoc_api.context[:workspace_id],
296
+ workspace_name: aoc_api.context[:workspace_name],
297
+ scope: scope
228
298
  )
229
299
  file_id = top_node_api.read("access_keys/#{top_node_api.app_info[:node_info]['access_key']}")[:data]['root_file_id'] if file_id.nil?
230
- node_plugin = Node.new(@agents.merge(
231
- skip_basic_auth_options: true,
232
- skip_node_options: true,
233
- node_api: top_node_api))
300
+ node_plugin = Node.new(@agents, api: top_node_api)
234
301
  case command_repo
235
302
  when *Node::COMMANDS_GEN4
236
303
  return node_plugin.execute_command_gen4(command_repo, file_id)
237
304
  when :transfer
238
305
  # client side is agent
239
- # server side is protocol server
306
+ # server side is transfer server
240
307
  # in same workspace
241
- # default is push
242
- case options.get_option(:operation, mandatory: true)
308
+ push_pull = options.get_next_argument('direction', expected: %i[push pull])
309
+ source_folder = options.get_next_argument('folder of source files', type: String)
310
+ case push_pull
243
311
  when :push
244
312
  client_direction = Fasp::TransferSpec::DIRECTION_SEND
245
- client_folder = options.get_option(:from_folder, mandatory: true)
313
+ client_folder = source_folder
246
314
  server_folder = transfer.destination_folder(client_direction)
247
315
  when :pull
248
316
  client_direction = Fasp::TransferSpec::DIRECTION_RECEIVE
249
317
  client_folder = transfer.destination_folder(client_direction)
250
- server_folder = options.get_option(:from_folder, mandatory: true)
318
+ server_folder = source_folder
319
+ else error_unreachable_line
251
320
  end
252
321
  client_apfid = top_node_api.resolve_api_fid(file_id, client_folder)
253
322
  server_apfid = top_node_api.resolve_api_fid(file_id, server_folder)
@@ -268,9 +337,9 @@ module Aspera
268
337
  server_apfid[:file_id],
269
338
  client_direction,
270
339
  add_ts)))
271
- else raise "INTERNAL ERROR: Missing case: #{command_repo}"
340
+ else error_unreachable_line
272
341
  end # command_repo
273
- # raise 'internal error:shall not reach here'
342
+ error_unreachable_line
274
343
  end # execute_nodegen4_command
275
344
 
276
345
  def execute_admin_action
@@ -282,14 +351,14 @@ module Aspera
282
351
  command_auth_prov = options.get_next_command(%i[list update])
283
352
  case command_auth_prov
284
353
  when :list
285
- providers = aoc_api.read('admin/auth_providers')[:data]
286
- return {type: :object_list, data: providers}
354
+ return result_list('admin/auth_providers')
287
355
  when :update
288
356
  raise 'not implemented'
289
357
  end
290
358
  when :subscription
291
359
  org = aoc_api.read('organization')[:data]
292
- bss_api = AoC.new(aoc_params('bss/platform'))
360
+ bss_api = api_from_options('bss/platform')
361
+ # cspell:disable
293
362
  graphql_query = "
294
363
  query ($organization_id: ID!) {
295
364
  aoc (organization_id: $organization_id) {
@@ -338,17 +407,18 @@ module Aspera
338
407
  }
339
408
  }
340
409
  "
410
+ # cspell:enable
341
411
  result = bss_api.create('graphql', {'variables' => {'organization_id' => org['id']}, 'query' => graphql_query})[:data]['data']
342
412
  return {type: :single_object, data: result['aoc']['bssSubscription']}
343
413
  when :ats
344
414
  ats_api = Rest.new(aoc_api.params.deep_merge({
345
- base_url: aoc_api.params[:base_url] + '/admin/ats/pub/v1',
415
+ base_url: "#{aoc_api.params[:base_url]}/admin/ats/pub/v1",
346
416
  auth: {scope: AoC::SCOPE_FILES_ADMIN_USER}
347
417
  }))
348
- return Ats.new(@agents.merge(skip_node_options: true)).execute_action_gen(ats_api)
418
+ return Ats.new(@agents).execute_action_gen(ats_api)
349
419
  when :analytics
350
420
  analytics_api = Rest.new(aoc_api.params.deep_merge({
351
- base_url: aoc_api.params[:base_url].gsub('/api/v1', '') + '/analytics/v2',
421
+ base_url: "#{aoc_api.params[:base_url].gsub('/api/v1', '')}/analytics/v2",
352
422
  auth: {scope: AoC::SCOPE_FILES_ADMIN_USER}
353
423
  }))
354
424
  command_analytics = options.get_next_command(%i[application_events transfers])
@@ -359,25 +429,28 @@ module Aspera
359
429
  return {type: :object_list, data: events}
360
430
  when :transfers
361
431
  event_type = command_analytics.to_s
362
- filter_resource = options.get_option(:name) || 'organizations'
363
- filter_id = options.get_option(:id) ||
432
+ filter_resource = options.get_next_argument('resource', expected: %i[organizations users nodes])
433
+ filter_id = options.get_next_argument('identifier', mandatory: false) ||
364
434
  case filter_resource
365
- when 'organizations' then aoc_api.current_user_info['organization_id']
366
- when 'users' then aoc_api.current_user_info['id']
367
- when 'nodes' then aoc_api.current_user_info['id'] # TODO: consistent ? # rubocop:disable Lint/DuplicateBranch
368
- else raise 'organizations or users for option --name'
435
+ when :organizations then aoc_api.current_user_info['organization_id']
436
+ when :users then aoc_api.current_user_info['id']
437
+ when :nodes then aoc_api.current_user_info['id'] # TODO: consistent ? # rubocop:disable Lint/DuplicateBranch
438
+ else error_unreachable_line
369
439
  end
370
440
  filter = options.get_option(:query) || {}
371
- raise 'query must be Hash' unless filter.is_a?(Hash)
372
441
  filter['limit'] ||= 100
373
442
  if options.get_option(:once_only, mandatory: true)
374
443
  saved_date = []
375
444
  start_date_persistency = PersistencyActionOnce.new(
376
445
  manager: @agents[:persistency],
377
446
  data: saved_date,
378
- ids: IdGenerator.from_list(['aoc_ana_date', options.get_option(:url, mandatory: true), current_workspace_info['name']].push(
379
- filter_resource,
380
- filter_id)))
447
+ id: IdGenerator.from_list([
448
+ 'aoc_ana_date',
449
+ options.get_option(:url, mandatory: true),
450
+ aoc_api.context(:files)[:workspace_name],
451
+ filter_resource.to_s,
452
+ filter_id
453
+ ]))
381
454
  start_date_time = saved_date.first
382
455
  stop_date_time = Time.now.utc.strftime('%FT%T.%LZ')
383
456
  # Log.log().error("start: #{start_date_time}")
@@ -388,7 +461,7 @@ module Aspera
388
461
  end
389
462
  events = analytics_api.read("#{filter_resource}/#{filter_id}/#{event_type}", query_read_delete(default: filter))[:data][event_type]
390
463
  start_date_persistency&.save
391
- if !options.get_option(:notif_to).nil?
464
+ if !options.get_option(:notify_to).nil?
392
465
  events.each do |tr_event|
393
466
  config.send_email_template(values: {ev: tr_event})
394
467
  end
@@ -404,7 +477,7 @@ module Aspera
404
477
  when :self, :organization then resource_type
405
478
  when :client_registration_token, :client_access_key then "admin/#{resource_type}s"
406
479
  when :application then 'admin/apps_new'
407
- when :dropbox then resource_type.to_s + 'es'
480
+ when :dropbox then "#{resource_type}es"
408
481
  when :kms_profile then "integrations/#{resource_type}s"
409
482
  else "#{resource_type}s"
410
483
  end
@@ -428,9 +501,7 @@ module Aspera
428
501
  id_result = 'token' if resource_class_path.eql?('admin/client_registration_tokens')
429
502
  # TODO: report inconsistency: creation url is !=, and does not return id.
430
503
  resource_class_path = 'admin/client_registration/token' if resource_class_path.eql?('admin/client_registration_tokens')
431
- list_or_one = options.get_next_argument('creation data', type: Hash)
432
- return do_bulk_operation(list_or_one, 'created', id_result: id_result) do |params|
433
- raise 'expecting Hash' unless params.is_a?(Hash)
504
+ return do_bulk_operation(command: command, descr: 'creation data', id_result: id_result) do |params|
434
505
  aoc_api.create(resource_class_path, params)[:data]
435
506
  end
436
507
  when :list
@@ -450,35 +521,38 @@ module Aspera
450
521
  when :group_membership then default_fields.push(*%w[group_id member_type member_id])
451
522
  when :workspace_membership then default_fields.push(*%w[workspace_id member_type member_id])
452
523
  end
453
- items = read_with_paging(resource_class_path, query_read_delete(default: default_query))
454
- formatter.display_item_count(items[:list].length, items[:total])
455
- return {type: :object_list, data: items[:list], fields: default_fields}
524
+ return result_list(resource_class_path, fields: default_fields, default_query: default_query)
456
525
  when :show
457
526
  object = aoc_api.read(resource_instance_path)[:data]
527
+ # default: show all, but certificate
458
528
  fields = object.keys.reject{|k|k.eql?('certificate')}
459
529
  return { type: :single_object, data: object, fields: fields }
460
530
  when :modify
461
- changes = options.get_next_argument('modified parameters (hash)')
462
- aoc_api.update(resource_instance_path, changes)
463
- return Main.result_status('modified')
531
+ changes = options.get_next_argument('properties', type: Hash)
532
+ return do_bulk_operation(command: command, descr: 'identifier', values: res_id) do |one_id|
533
+ aoc_api.update("#{resource_class_path}/#{one_id}", changes)
534
+ {'id' => one_id}
535
+ end
464
536
  when :delete
465
- return do_bulk_operation(res_id, 'deleted') do |one_id|
537
+ return do_bulk_operation(command: command, descr: 'identifier', values: res_id) do |one_id|
466
538
  aoc_api.delete("#{resource_class_path}/#{one_id}")
467
539
  {'id' => one_id}
468
540
  end
469
541
  when :set_pub_key
470
542
  # special : reads private and generate public
471
- the_private_key = options.get_next_argument('private_key')
543
+ the_private_key = options.get_next_argument('private_key PEM value', type: String)
472
544
  the_public_key = OpenSSL::PKey::RSA.new(the_private_key).public_key.to_s
473
545
  aoc_api.update(resource_instance_path, {jwt_grant_enabled: true, public_key: the_public_key})
474
546
  return Main.result_success
475
547
  when :do
476
548
  command_repo = options.get_next_command(NODE4_EXT_COMMANDS)
549
+ # init context
550
+ aoc_api.context(:files)
477
551
  return execute_nodegen4_command(command_repo, res_id)
478
- else raise 'unknown command'
552
+ else error_unexpected_value(command)
479
553
  end
480
554
  when :usage_reports
481
- return {type: :object_list, data: aoc_api.read('usage_reports', {workspace_id: current_workspace_info['id']})[:data]}
555
+ return result_list('usage_reports', base_query: {workspace_id: aoc_api.context(:files)[:workspace_id]})
482
556
  end
483
557
  end
484
558
 
@@ -487,6 +561,15 @@ module Aspera
487
561
 
488
562
  def execute_action
489
563
  command = options.get_next_command(ACTIONS)
564
+ if %i[files packages].include?(command)
565
+ default_flag = ' (default)' if options.get_option(:workspace).eql?(:default)
566
+ app_context = aoc_api.context(command)
567
+ formatter.display_status("Workspace: #{app_context[:workspace_name].to_s.red}#{default_flag}")
568
+ if !aoc_api.private_link.nil?
569
+ folder_name = aoc_api.node_api_from(node_id: app_context[:home_node_id]).read("files/#{app_context[:home_file_id]}")[:data]['name']
570
+ formatter.display_status("Private Folder: #{folder_name}")
571
+ end
572
+ end
490
573
  case command
491
574
  when :reminder
492
575
  # send an email reminder with list of orgs
@@ -502,53 +585,59 @@ module Aspera
502
585
  when :tier_restrictions
503
586
  return { type: :single_object, data: aoc_api.read('tier_restrictions')[:data] }
504
587
  when :user
505
- case options.get_next_command(%i[workspaces profile])
588
+ case options.get_next_command(%i[workspaces profile preferences])
506
589
  # when :settings
507
590
  # return {type: :object_list,data: aoc_api.read('client_settings/')[:data]}
508
591
  when :workspaces
509
592
  case options.get_next_command(%i[list current])
510
593
  when :list
511
- return {type: :object_list, data: aoc_api.read('workspaces')[:data], fields: %w[id name]}
594
+ return result_list('workspaces', fields: %w[id name])
512
595
  when :current
513
- return { type: :single_object, data: current_workspace_info }
596
+ return { type: :single_object, data: aoc_api.read("workspaces/#{aoc_api.context(:files)[:workspace_id]}")[:data] }
514
597
  end
515
598
  when :profile
516
599
  case options.get_next_command(%i[show modify])
517
600
  when :show
518
601
  return { type: :single_object, data: aoc_api.current_user_info(exception: true) }
519
602
  when :modify
520
- aoc_api.update("users/#{aoc_api.current_user_info(exception: true)['id']}", options.get_next_argument('modified parameters (hash)'))
603
+ aoc_api.update("users/#{aoc_api.current_user_info(exception: true)['id']}", options.get_next_argument('properties', type: Hash))
604
+ return Main.result_status('modified')
605
+ end
606
+ when :preferences
607
+ user_preferences_res = "users/#{aoc_api.current_user_info(exception: true)['id']}/user_interaction_preferences"
608
+ case options.get_next_command(%i[show modify])
609
+ when :show
610
+ return { type: :single_object, data: aoc_api.read(user_preferences_res)[:data] }
611
+ when :modify
612
+ aoc_api.update(user_preferences_res, options.get_next_argument('properties', type: Hash))
521
613
  return Main.result_status('modified')
522
614
  end
523
615
  end
524
616
  when :packages
525
- package_command = options.get_next_command(%i[shared_inboxes send recv list show delete].concat(Node::NODE4_READ_ACTIONS))
617
+ package_command = options.get_next_command(%i[shared_inboxes send receive list show delete].concat(Node::NODE4_READ_ACTIONS), aliases: {recv: :receive})
526
618
  case package_command
527
619
  when :shared_inboxes
528
620
  case options.get_next_command(%i[list show])
529
621
  when :list
530
- query = query_read_delete
531
- if query.nil?
532
- query = {'embed[]' => 'dropbox', 'aggregate_permissions_by_dropbox' => true, 'sort' => 'dropbox_name'}
533
- query['workspace_id'] = current_workspace_info['id'] unless current_workspace_info['id'].eql?(:undefined)
534
- end
535
- return {type: :object_list, data: aoc_api.read('dropbox_memberships', query)[:data], fields: ['dropbox_id', 'dropbox.name']}
622
+ default_query = {'embed[]' => 'dropbox', 'aggregate_permissions_by_dropbox' => true, 'sort' => 'dropbox_name'}
623
+ default_query['workspace_id'] = aoc_api.context[:workspace_id] unless aoc_api.context[:workspace_id].eql?(:undefined)
624
+ return result_list('dropbox_memberships', fields: %w[dropbox_id dropbox.name], default_query: default_query)
536
625
  when :show
537
626
  return {type: :single_object, data: aoc_api.read(get_resource_path_from_args('dropboxes'), query)[:data]}
538
627
  end
539
628
  when :send
540
- package_data = value_create_modify(command: package_command, type: Hash)
629
+ package_data = value_create_modify(command: package_command)
541
630
  new_user_option = options.get_option(:new_user_option)
542
631
  option_validate = options.get_option(:validate_metadata)
543
632
  # works for both normal usr auth and link auth
544
- package_data['workspace_id'] ||= current_workspace_info['id']
633
+ package_data['workspace_id'] ||= aoc_api.context[:workspace_id]
545
634
 
546
- if !aoc_api.url_token_data.nil?
547
- assert_public_link_types(%w[send_package_to_user send_package_to_dropbox])
548
- box_type = aoc_api.url_token_data['purpose'].split('_').last
549
- package_data['recipients'] = [{'id' => aoc_api.url_token_data['data']["#{box_type}_id"], 'type' => box_type}]
635
+ if !aoc_api.public_link.nil?
636
+ aoc_api.assert_public_link_types(%w[send_package_to_user send_package_to_dropbox])
637
+ box_type = aoc_api.public_link['purpose'].split('_').last
638
+ package_data['recipients'] = [{'id' => aoc_api.public_link['data']["#{box_type}_id"], 'type' => box_type}]
550
639
  # enforce workspace id from link (should be already ok, but in case user wanted to override)
551
- package_data['workspace_id'] = aoc_api.url_token_data['data']['workspace_id']
640
+ package_data['workspace_id'] = aoc_api.public_link['data']['workspace_id']
552
641
  end
553
642
 
554
643
  # transfer may raise an error
@@ -556,41 +645,47 @@ module Aspera
556
645
  Main.result_transfer(transfer.start(created_package[:spec], rest_token: created_package[:node]))
557
646
  # return all info on package (especially package id)
558
647
  return { type: :single_object, data: created_package[:info]}
559
- when :recv
560
- if !aoc_api.url_token_data.nil?
561
- assert_public_link_types(['view_received_package'])
562
- options.set_option(:id, aoc_api.url_token_data['data']['package_id'])
648
+ when :receive
649
+ ids_to_download = nil
650
+ if !aoc_api.public_link.nil?
651
+ aoc_api.assert_public_link_types(['view_received_package'])
652
+ # set the package id, it will
653
+ ids_to_download = aoc_api.public_link['data']['package_id']
563
654
  end
564
- # scalar here
565
- ids_to_download = instance_identifier
655
+ # get from command line unless it was a public link
656
+ ids_to_download ||= instance_identifier
566
657
  skip_ids_data = []
567
658
  skip_ids_persistency = nil
568
659
  if options.get_option(:once_only, mandatory: true)
660
+ # TODO: add query info to id
569
661
  skip_ids_persistency = PersistencyActionOnce.new(
570
662
  manager: @agents[:persistency],
571
663
  data: skip_ids_data,
572
- id: IdGenerator.from_list(['aoc_recv', options.get_option(:url, mandatory: true),
573
- current_workspace_info['id']].concat(aoc_api.additional_persistence_ids)))
664
+ id: IdGenerator.from_list(
665
+ ['aoc_recv',
666
+ options.get_option(:url, mandatory: true),
667
+ aoc_api.context[:workspace_id]
668
+ ].concat(aoc_api.additional_persistence_ids)))
574
669
  end
575
- if VAL_ALL.eql?(ids_to_download)
576
- query = query_read_delete(default: PACKAGE_QUERY_DEFAULT)
577
- raise 'option query must be Hash' unless query.is_a?(Hash)
578
- if query.key?('dropbox_name')
579
- # convenience: specify name instead of id
580
- raise 'not both dropbox_name and dropbox_id' if query.key?('dropbox_id')
581
- query['dropbox_id'] = aoc_api.lookup_by_name('dropboxes', query['dropbox_name'])['id']
582
- query.delete('dropbox_name')
583
- end
584
- query['workspace_id'] ||= current_workspace_info['id'] unless current_workspace_info['id'].eql?(:undefined)
585
- # get list of packages in inbox
586
- package_info = aoc_api.read('packages', query)[:data]
670
+ case ids_to_download
671
+ when ExtendedValue::ALL, ExtendedValue::INIT
672
+ query = query_read_delete(default: PACKAGE_RECEIVED_BASE_QUERY)
673
+ assert_type(query, Hash){'query'}
674
+ resolve_dropbox_name_default_ws_id(query)
587
675
  # remove from list the ones already downloaded
588
- ids_to_download = package_info.map{|e|e['id']}
676
+ all_ids = api_read_all('packages', query)[:data].map{|e|e['id']}
677
+ if ids_to_download.eql?(ExtendedValue::INIT)
678
+ assert(skip_ids_persistency){'Only with option once_only'}
679
+ skip_ids_persistency.data.clear.concat(all_ids)
680
+ skip_ids_persistency.save
681
+ return Main.result_status("Initialized skip for #{skip_ids_persistency.data.count} package(s)")
682
+ end
589
683
  # array here
590
- ids_to_download.reject!{|id|skip_ids_data.include?(id)}
591
- end # VAL_ALL
684
+ ids_to_download = all_ids.reject{|id|skip_ids_data.include?(id)}
685
+ else
686
+ ids_to_download = [ids_to_download] unless ids_to_download.is_a?(Array)
687
+ end # ExtendedValue::ALL
592
688
  # list here
593
- ids_to_download = [ids_to_download] unless ids_to_download.is_a?(Array)
594
689
  result_transfer = []
595
690
  formatter.display_status("found #{ids_to_download.length} package(s).")
596
691
  ids_to_download.each do |package_id|
@@ -598,8 +693,8 @@ module Aspera
598
693
  formatter.display_status("downloading package: #{package_info['name']}")
599
694
  package_node_api = aoc_api.node_api_from(
600
695
  node_id: package_info['node_id'],
601
- workspace_id: current_workspace_info['id'],
602
- workspace_name: current_workspace_info['name'],
696
+ workspace_id: aoc_api.context[:workspace_id],
697
+ workspace_name: aoc_api.context[:workspace_name],
603
698
  package_info: package_info)
604
699
  statuses = transfer.start(
605
700
  package_node_api.transfer_spec_gen4(
@@ -616,61 +711,48 @@ module Aspera
616
711
  end
617
712
  return Main.result_transfer_multiple(result_transfer)
618
713
  when :show
619
- package_id = options.get_next_argument('package ID')
714
+ package_id = instance_identifier
620
715
  package_info = aoc_api.read("packages/#{package_id}")[:data]
621
716
  return { type: :single_object, data: package_info }
622
717
  when :list
623
718
  display_fields = %w[id name bytes_transferred]
624
- query = query_read_delete(default: PACKAGE_QUERY_DEFAULT)
625
- raise 'option query must be Hash' unless query.is_a?(Hash)
626
- if query.key?('dropbox_name')
627
- # convenience: specify name instead of id
628
- raise 'not both dropbox_name and dropbox_id' if query.key?('dropbox_id')
629
- query['dropbox_id'] = aoc_api.lookup_by_name('dropboxes', query['dropbox_name'])['id']
630
- query.delete('dropbox_name')
631
- end
632
- if current_workspace_info['id'].eql?(:undefined)
633
- display_fields.push('workspace_id')
634
- else
635
- query['workspace_id'] ||= current_workspace_info['id']
636
- end
637
- packages = aoc_api.read('packages', query)[:data]
638
- return {type: :object_list, data: packages, fields: display_fields}
719
+ display_fields.push('workspace_id') if aoc_api.context[:workspace_id].eql?(:undefined)
720
+ return result_list('packages', fields: display_fields, base_query: PACKAGE_RECEIVED_BASE_QUERY) do |query|
721
+ resolve_dropbox_name_default_ws_id(query)
722
+ end
639
723
  when :delete
640
- list_or_one = instance_identifier
641
- return do_bulk_operation(list_or_one, 'deleted') do |id|
642
- raise 'expecting String identifier' unless id.is_a?(String) || id.is_a?(Integer)
724
+ return do_bulk_operation(command: package_command, descr: 'identifier', values: identifier) do |id|
725
+ assert_values(id.class, [String, Integer]){'identifier'}
643
726
  aoc_api.delete("packages/#{id}")[:data]
644
727
  end
645
728
  when *Node::NODE4_READ_ACTIONS
646
- package_id = options.get_next_argument('package ID')
729
+ package_id = instance_identifier
647
730
  package_info = aoc_api.read("packages/#{package_id}")[:data]
648
- return execute_nodegen4_command(package_command, package_info['node_id'], file_id: package_info['file_id'], scope: AoC::SCOPE_NODE_USER)
731
+ return execute_nodegen4_command(package_command, package_info['node_id'], file_id: package_info['file_id'], scope: Aspera::Node::SCOPE_USER)
649
732
  end
650
733
  when :files
651
734
  command_repo = options.get_next_command([:short_link].concat(NODE4_EXT_COMMANDS))
652
735
  case command_repo
653
736
  when *NODE4_EXT_COMMANDS
654
- return execute_nodegen4_command(command_repo, home_info[:node_id], file_id: home_info[:file_id], scope: AoC::SCOPE_NODE_USER)
737
+ return execute_nodegen4_command(command_repo, aoc_api.context[:home_node_id], file_id: aoc_api.context[:home_file_id], scope: Aspera::Node::SCOPE_USER)
655
738
  when :short_link
656
- # execute action on AoC API
657
- short_link_command = options.get_next_command(%i[create delete list])
658
- folder_dest = options.get_next_argument('path')
659
739
  link_type = options.get_next_argument('link type', expected: %i[public private])
740
+ short_link_command = options.get_next_command(%i[create delete list])
741
+ folder_dest = options.get_next_argument('path', type: String)
660
742
  home_node_api = aoc_api.node_api_from(
661
- node_id: home_info[:node_id],
662
- workspace_id: current_workspace_info['id'],
663
- workspace_name: current_workspace_info['name'])
664
- shared_apfid = home_node_api.resolve_api_fid(home_info[:file_id], folder_dest)
743
+ node_id: aoc_api.context[:home_node_id],
744
+ workspace_id: aoc_api.context[:workspace_id],
745
+ workspace_name: aoc_api.context[:workspace_name])
746
+ shared_apfid = home_node_api.resolve_api_fid(aoc_api.context[:home_file_id], folder_dest)
665
747
  folder_info = {
666
748
  node_id: shared_apfid[:api].app_info[:node_info]['id'],
667
749
  file_id: shared_apfid[:file_id],
668
- workspace_id: current_workspace_info['id']
750
+ workspace_id: aoc_api.context[:workspace_id]
669
751
  }
670
752
  purpose = case link_type
671
753
  when :public then 'token_auth_redirection'
672
754
  when :private then 'shared_folder_auth_link'
673
- else raise 'internal error'
755
+ else error_unreachable_line
674
756
  end
675
757
  case short_link_command
676
758
  when :delete
@@ -699,14 +781,12 @@ module Aspera
699
781
  end
700
782
  list_params = {
701
783
  json_query: query.to_json,
702
- edit_access: true,
703
784
  purpose: purpose,
785
+ edit_access: true,
704
786
  # embed: 'updated_by_user',
705
787
  sort: '-created_at'
706
788
  }
707
- result = aoc_api.read('short_links', list_params)[:data]
708
- result.each{|i|i.delete('data')}
709
- return {type: :object_list, data: result}
789
+ return result_list('short_links', fields: Formatter.all_but('data'), base_query: list_params)
710
790
  when :create
711
791
  creation_params = {
712
792
  purpose: purpose,
@@ -741,8 +821,8 @@ module Aspera
741
821
  'access_levels' => access_levels,
742
822
  'tags' => {
743
823
  'url_token' => true,
744
- 'workspace_id' => current_workspace_info['id'],
745
- 'workspace_name' => current_workspace_info['name'],
824
+ 'workspace_id' => aoc_api.context[:workspace_id],
825
+ 'workspace_name' => aoc_api.context[:workspace_name],
746
826
  'folder_name' => folder_name,
747
827
  'created_by_name' => aoc_api.current_user_info['name'],
748
828
  'created_by_email' => aoc_api.current_user_info['email'],
@@ -772,7 +852,7 @@ module Aspera
772
852
  wf_command = options.get_next_command(%i[action launch].concat(Plugin::ALL_OPS))
773
853
  case wf_command
774
854
  when *Plugin::ALL_OPS
775
- return entity_command(wf_command, automation_api, 'workflows', id_default: :id)
855
+ return entity_command(wf_command, automation_api, 'workflows')
776
856
  when :launch
777
857
  wf_id = instance_identifier
778
858
  data = automation_api.create("workflows/#{wf_id}/launch", {})[:data]
@@ -794,106 +874,22 @@ module Aspera
794
874
  return execute_admin_action
795
875
  when :gateway
796
876
  require 'aspera/faspex_gw'
797
- url = value_create_modify(type: String)
877
+ url = value_create_modify(command: command, type: String)
798
878
  uri = URI.parse(url)
799
879
  server = WebServerSimple.new(uri)
800
- server.mount(uri.path, Faspex4GWServlet, aoc_api, current_workspace_info['id'])
880
+ server.mount(uri.path, Faspex4GWServlet, aoc_api, aoc_api.context(:files)[:workspace_id])
801
881
  trap('INT') { server.shutdown }
802
882
  formatter.display_status("Faspex 4 gateway listening on #{url}")
803
883
  Log.log.info("Listening on #{url}")
804
884
  # this is blocking until server exits
805
885
  server.start
806
886
  return Main.result_status('Gateway terminated')
807
- else
808
- raise "internal error: #{command}"
887
+ else error_unreachable_line
809
888
  end # action
810
- raise 'internal error: command shall return'
811
- end
812
-
813
- # @param [Hash] params : plugin_sym, instance_url
814
- # @return [Hash] :preset_value, :test_args
815
- def wizard(params)
816
- if params[:prepare]
817
- organization = AoC.parse_url(params[:instance_url]).first
818
- # if not defined by user, generate name
819
- params[:preset_name] ||= [params[:plugin_sym], organization].join('_')
820
- params[:need_private_key] = true
821
- return
822
- end
823
- options.set_option(:private_key, '@file:' + params[:private_key_path])
824
- # make username mandatory for jwt, this triggers interactive input
825
- options.get_option(:username, mandatory: true)
826
- auto_set_pub_key = false
827
- auto_set_jwt = false
828
- use_browser_authentication = false
829
- if options.get_option(:use_generic_client)
830
- formatter.display_status('Using global client_id.')
831
- formatter.display_status('Please Login to your Aspera on Cloud instance.'.red)
832
- formatter.display_status('Navigate to your "Account Settings"'.red)
833
- formatter.display_status('Check or update the value of "Public Key" to be:'.red.blink)
834
- formatter.display_status(params[:pub_key_pem])
835
- if !options.get_option(:test_mode)
836
- formatter.display_status('Once updated or validated, press enter.')
837
- OpenApplication.instance.uri(params[:instance_url])
838
- $stdin.gets
839
- end
840
- else
841
- formatter.display_status('Using organization specific client_id.')
842
- if options.get_option(:client_id).nil? || options.get_option(:client_secret).nil?
843
- formatter.display_status('Please login to your Aspera on Cloud instance.'.red)
844
- formatter.display_status('Go to: Apps->Admin->Organization->Integrations')
845
- formatter.display_status('Create or check if there is an existing integration named:')
846
- formatter.display_status("- name: #{@info[:name]}")
847
- formatter.display_status("- redirect uri: #{DEFAULT_REDIRECT}")
848
- formatter.display_status('- origin: localhost')
849
- formatter.display_status('Once created or identified,')
850
- formatter.display_status('Please enter:'.red)
851
- end
852
- OpenApplication.instance.uri("#{params[:instance_url]}/#{AOC_PATH_API_CLIENTS}")
853
- options.get_option(:client_id, mandatory: true)
854
- options.get_option(:client_secret, mandatory: true)
855
- use_browser_authentication = true
856
- end
857
- if use_browser_authentication
858
- formatter.display_status('We will use web authentication to bootstrap.')
859
- auto_set_pub_key = true
860
- auto_set_jwt = true
861
- aoc_api.oauth.generic_parameters[:grant_method] = :web
862
- aoc_api.oauth.generic_parameters[:scope] = AoC::SCOPE_FILES_ADMIN
863
- aoc_api.oauth.specific_parameters[:redirect_uri] = DEFAULT_REDIRECT
864
- end
865
- myself = aoc_api.read('self')[:data]
866
- if auto_set_pub_key
867
- raise CliError, 'Public key is already set in profile (use --override=yes)' unless myself['public_key'].empty? || option_override
868
- formatter.display_status('Updating profile with new key')
869
- aoc_api.update("users/#{myself['id']}", {'public_key' => params[:pub_key_pem]})
870
- end
871
- if auto_set_jwt
872
- formatter.display_status('Enabling JWT for client')
873
- aoc_api.update("clients/#{options.get_option(:client_id)}", {'jwt_grant_enabled' => true, 'explicit_authorization_required' => false})
874
- end
875
- formatter.display_status("Creating new config preset: #{params[:preset_name]}")
876
- preset_result = {
877
- url: params[:instance_url],
878
- username: myself['email'],
879
- auth: :jwt.to_s,
880
- private_key: '@file:' + params[:private_key_path]
881
- }.stringify_keys
882
- # set only if non nil
883
- %i[client_id client_secret].each do |s|
884
- o = options.get_option(s)
885
- preset_result[s.to_s] = o unless o.nil?
886
- end
887
- return {
888
- preset_value: preset_result,
889
- test_args: "#{params[:plugin_sym]} user profile show"
890
- }
889
+ error_unreachable_line
891
890
  end
892
891
 
893
- private :aoc_params,
894
- :home_info,
895
- :assert_public_link_types,
896
- :execute_admin_action
892
+ private :execute_admin_action
897
893
  end # AoC
898
894
  end # Plugins
899
895
  end # Cli