aspera-cli 4.10.0 → 4.12.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 (97) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/BUGS.md +19 -0
  4. data/CHANGELOG.md +528 -0
  5. data/CONTRIBUTING.md +143 -0
  6. data/README.md +977 -589
  7. data/bin/ascli +4 -4
  8. data/bin/asession +12 -12
  9. data/docs/test_env.conf +29 -19
  10. data/examples/aoc.rb +6 -6
  11. data/examples/dascli +18 -16
  12. data/examples/faspex4.rb +15 -15
  13. data/examples/node.rb +12 -12
  14. data/examples/proxy.pac +2 -2
  15. data/examples/server.rb +12 -12
  16. data/lib/aspera/aoc.rb +344 -272
  17. data/lib/aspera/ascmd.rb +56 -54
  18. data/lib/aspera/ats_api.rb +4 -4
  19. data/lib/aspera/cli/basic_auth_plugin.rb +15 -12
  20. data/lib/aspera/cli/extended_value.rb +9 -9
  21. data/lib/aspera/cli/{formater.rb → formatter.rb} +69 -69
  22. data/lib/aspera/cli/listener/line_dump.rb +1 -1
  23. data/lib/aspera/cli/listener/logger.rb +1 -1
  24. data/lib/aspera/cli/listener/progress.rb +5 -6
  25. data/lib/aspera/cli/listener/progress_multi.rb +16 -21
  26. data/lib/aspera/cli/main.rb +72 -73
  27. data/lib/aspera/cli/manager.rb +112 -112
  28. data/lib/aspera/cli/plugin.rb +68 -48
  29. data/lib/aspera/cli/plugins/alee.rb +4 -4
  30. data/lib/aspera/cli/plugins/aoc.rb +322 -720
  31. data/lib/aspera/cli/plugins/ats.rb +50 -52
  32. data/lib/aspera/cli/plugins/bss.rb +10 -10
  33. data/lib/aspera/cli/plugins/config.rb +514 -410
  34. data/lib/aspera/cli/plugins/console.rb +12 -12
  35. data/lib/aspera/cli/plugins/cos.rb +18 -20
  36. data/lib/aspera/cli/plugins/faspex.rb +134 -136
  37. data/lib/aspera/cli/plugins/faspex5.rb +235 -70
  38. data/lib/aspera/cli/plugins/node.rb +378 -309
  39. data/lib/aspera/cli/plugins/orchestrator.rb +52 -49
  40. data/lib/aspera/cli/plugins/preview.rb +129 -120
  41. data/lib/aspera/cli/plugins/server.rb +137 -83
  42. data/lib/aspera/cli/plugins/shares.rb +77 -52
  43. data/lib/aspera/cli/plugins/sync.rb +13 -33
  44. data/lib/aspera/cli/transfer_agent.rb +61 -61
  45. data/lib/aspera/cli/version.rb +2 -1
  46. data/lib/aspera/colors.rb +3 -3
  47. data/lib/aspera/command_line_builder.rb +78 -74
  48. data/lib/aspera/cos_node.rb +31 -29
  49. data/lib/aspera/data_repository.rb +1 -1
  50. data/lib/aspera/environment.rb +30 -28
  51. data/lib/aspera/fasp/agent_base.rb +17 -15
  52. data/lib/aspera/fasp/agent_connect.rb +34 -32
  53. data/lib/aspera/fasp/agent_direct.rb +70 -73
  54. data/lib/aspera/fasp/agent_httpgw.rb +79 -74
  55. data/lib/aspera/fasp/agent_node.rb +26 -26
  56. data/lib/aspera/fasp/agent_trsdk.rb +20 -20
  57. data/lib/aspera/fasp/error.rb +3 -2
  58. data/lib/aspera/fasp/error_info.rb +11 -8
  59. data/lib/aspera/fasp/installation.rb +80 -80
  60. data/lib/aspera/fasp/listener.rb +2 -2
  61. data/lib/aspera/fasp/parameters.rb +103 -92
  62. data/lib/aspera/fasp/parameters.yaml +313 -214
  63. data/lib/aspera/fasp/resume_policy.rb +10 -10
  64. data/lib/aspera/fasp/transfer_spec.rb +22 -2
  65. data/lib/aspera/fasp/uri.rb +7 -7
  66. data/lib/aspera/faspex_gw.rb +80 -159
  67. data/lib/aspera/faspex_postproc.rb +77 -0
  68. data/lib/aspera/hash_ext.rb +3 -3
  69. data/lib/aspera/id_generator.rb +5 -5
  70. data/lib/aspera/keychain/encrypted_hash.rb +23 -28
  71. data/lib/aspera/keychain/macos_security.rb +21 -20
  72. data/lib/aspera/log.rb +13 -13
  73. data/lib/aspera/nagios.rb +24 -23
  74. data/lib/aspera/node.rb +217 -38
  75. data/lib/aspera/oauth.rb +78 -74
  76. data/lib/aspera/open_application.rb +19 -11
  77. data/lib/aspera/persistency_action_once.rb +4 -4
  78. data/lib/aspera/persistency_folder.rb +13 -13
  79. data/lib/aspera/preview/file_types.rb +8 -8
  80. data/lib/aspera/preview/generator.rb +67 -67
  81. data/lib/aspera/preview/utils.rb +27 -27
  82. data/lib/aspera/proxy_auto_config.js +63 -63
  83. data/lib/aspera/proxy_auto_config.rb +19 -19
  84. data/lib/aspera/rest.rb +65 -67
  85. data/lib/aspera/rest_call_error.rb +2 -1
  86. data/lib/aspera/rest_error_analyzer.rb +22 -21
  87. data/lib/aspera/rest_errors_aspera.rb +16 -16
  88. data/lib/aspera/secret_hider.rb +17 -14
  89. data/lib/aspera/ssh.rb +15 -14
  90. data/lib/aspera/sync.rb +177 -62
  91. data/lib/aspera/temp_file_manager.rb +2 -2
  92. data/lib/aspera/uri_reader.rb +4 -4
  93. data/lib/aspera/web_auth.rb +13 -64
  94. data/lib/aspera/web_server_simple.rb +76 -0
  95. data.tar.gz.sig +0 -0
  96. metadata +11 -6
  97. metadata.gz.sig +0 -0
@@ -16,334 +16,95 @@ require 'date'
16
16
  module Aspera
17
17
  module Cli
18
18
  module Plugins
19
- class Aoc < BasicAuthPlugin
19
+ class Aoc < Aspera::Cli::BasicAuthPlugin
20
20
  class << self
21
21
  def detect(base_url)
22
22
  api = Rest.new({base_url: base_url})
23
23
  # either in standard domain, or product name in page
24
24
  if URI.parse(base_url).host.end_with?(Aspera::AoC::PROD_DOMAIN) ||
25
25
  api.call({operation: 'GET', redirect_max: 1, headers: {'Accept' => 'text/html'}})[:http].body.include?(Aspera::AoC::PRODUCT_NAME)
26
- return {product: :aoc,version: 'SaaS' }
26
+ return {product: :aoc, version: 'SaaS' }
27
27
  end
28
28
  return nil
29
29
  end
30
30
  end
31
31
  # special value for package id
32
- VAL_ALL = 'ALL'
33
- ID_AK_ADMIN = 'ASPERA_ACCESS_KEY_ADMIN'
34
- KNOWN_AOC_RES=%i[
35
- self organization user group group_membership client contact dropbox node operation package saml_configuration
36
- workspace workspace_membership dropbox_membership short_link application client_registration_token client_access_key
32
+ KNOWN_AOC_RES = %i[
33
+ self
34
+ organization
35
+ user
36
+ group
37
+ group_membership
38
+ client
39
+ contact
40
+ dropbox
41
+ node
42
+ operation
43
+ package
44
+ saml_configuration
45
+ workspace
46
+ workspace_membership
47
+ dropbox_membership
48
+ short_link
49
+ application
50
+ client_registration_token
51
+ client_access_key
37
52
  kms_profile].freeze
53
+ ENTITY_NAME_SPECIFIER = 'name'
54
+ PACKAGE_QUERY_DEFAULT = {'archived' => false, 'exclude_dropbox_packages' => true, 'has_content' => true, 'received' => true}.freeze
38
55
 
39
56
  def initialize(env)
40
57
  super(env)
41
- @workspace_info = nil
42
- @persist_ids = nil
43
- @home_node_file = nil
44
- @api_aoc = nil
45
- @url_token_data = nil
46
- @api_aoc = nil
47
- options.add_opt_list(:auth,Oauth::STD_AUTH_TYPES,'OAuth type of authentication')
48
- options.add_opt_list(:operation, %i[push pull],'client operation for transfers')
49
- options.add_opt_simple(:client_id,'OAuth API client identifier in application')
50
- options.add_opt_simple(:client_secret,'OAuth API client passcode')
51
- options.add_opt_simple(:redirect_uri,'OAuth API client redirect URI')
52
- options.add_opt_simple(:private_key,'OAuth JWT RSA private key PEM value (prefix file path with @file:)')
53
- options.add_opt_simple(:passphrase,'RSA private key passphrase')
54
- options.add_opt_simple(:workspace,'name of workspace')
55
- options.add_opt_simple(:name,'resource name')
56
- options.add_opt_simple(:path,'file or folder path')
57
- options.add_opt_simple(:link,'public link to shared resource')
58
- options.add_opt_simple(:new_user_option,'new user creation option for unknown package recipients')
59
- options.add_opt_simple(:from_folder,'share to share source folder')
60
- options.add_opt_simple(:scope,'OAuth scope for AoC API calls')
61
- options.add_opt_boolean(:default_ports,'use standard FASP ports or get from node api')
62
- options.add_opt_boolean(:validate_metadata,'validate shared inbox metadata')
63
- options.set_option(:default_ports,:yes)
64
- options.set_option(:validate_metadata,:yes)
65
- options.set_option(:new_user_option,{'package_contact' => true})
66
- options.set_option(:operation,:push)
67
- options.set_option(:auth,:jwt)
68
- options.set_option(:scope,AoC::SCOPE_FILES_USER)
69
- options.set_option(:private_key,'@file:' + env[:private_key_path]) if env[:private_key_path].is_a?(String)
70
- options.set_option(:workspace,:default)
58
+ @cache_workspace_info = nil
59
+ @cache_home_node_file = nil
60
+ @cache_api_aoc = nil
61
+ options.add_opt_list(:auth, Oauth::STD_AUTH_TYPES, 'OAuth type of authentication')
62
+ options.add_opt_list(:operation, %i[push pull], 'client operation for transfers')
63
+ options.add_opt_simple(:client_id, 'OAuth API client identifier')
64
+ options.add_opt_simple(:client_secret, 'OAuth API client secret')
65
+ options.add_opt_simple(:redirect_uri, 'OAuth API client redirect URI')
66
+ options.add_opt_simple(:private_key, 'OAuth JWT RSA private key PEM value (prefix file path with @file:)')
67
+ options.add_opt_simple(:scope, 'OAuth scope for AoC API calls')
68
+ options.add_opt_simple(:passphrase, 'RSA private key passphrase')
69
+ options.add_opt_simple(:workspace, 'Name of workspace')
70
+ options.add_opt_simple(:name, "Resource name (prefer to use keyword #{ENTITY_NAME_SPECIFIER})")
71
+ options.add_opt_simple(:link, 'Public link to shared resource')
72
+ options.add_opt_simple(:new_user_option, 'New user creation option for unknown package recipients')
73
+ options.add_opt_simple(:from_folder, 'Source folder for Folder-to-Folder transfer')
74
+ options.add_opt_boolean(:validate_metadata, 'Validate shared inbox metadata')
75
+ options.set_option(:validate_metadata, :yes)
76
+ options.set_option(:operation, :push)
77
+ options.set_option(:auth, :jwt)
78
+ options.set_option(:scope, AoC::SCOPE_FILES_USER)
79
+ options.set_option(:private_key, '@file:' + env[:private_key_path]) if env[:private_key_path].is_a?(String)
80
+ options.set_option(:workspace, :default)
71
81
  options.parse_options!
72
- AoC.use_standard_ports = options.get_option(:default_ports)
73
- return if env[:man_only]
82
+ # add node plugin options
83
+ Node.new(env.merge({man_only: true, skip_basic_auth_options: true}))
84
+ end
85
+
86
+ # build list of options for AoC API, based on options of CLI
87
+ def aoc_params(subpath)
88
+ # copy command line options to args
89
+ return Aspera::AoC::OPTIONS_NEW.each_with_object({subpath: subpath}){|i, m|m[i] = options.get_option(i)}
74
90
  end
75
91
 
76
92
  def aoc_api
77
- if @api_aoc.nil?
78
- @api_aoc = AoC.new(aoc_params(AoC::API_V1))
93
+ if @cache_api_aoc.nil?
94
+ @cache_api_aoc = AoC.new(aoc_params(AoC::API_V1))
79
95
  # add keychain for access key secrets
80
- @api_aoc.key_chain = @agents[:config]
96
+ @cache_api_aoc.secret_finder = @agents[:config]
81
97
  end
82
- return @api_aoc
83
- end
84
-
85
- # starts transfer using transfer agent
86
- def transfer_start(app,direction,node_file,ts_add)
87
- ts_add.deep_merge!(AoC.analytics_ts(app,direction,@workspace_info['id'],@workspace_info['name']))
88
- ts_add.deep_merge!(aoc_api.console_ts(app))
89
- return transfer.start(*aoc_api.tr_spec(app,direction,node_file,ts_add))
90
- end
91
-
92
- NODE4_CMD_PATH = %i[bearer_token_node node_info browse find]
93
- NODE4_COMMANDS = [NODE4_CMD_PATH,%i[mkdir rename delete upload download transfer http_node_download v3 file]].flatten.freeze
94
-
95
- def execute_node_gen4_command(command_repo,top_node_file)
96
- case command_repo
97
- when :bearer_token_node
98
- thepath = options.get_next_argument('path')
99
- node_file = aoc_api.resolve_node_file(top_node_file,thepath)
100
- node_api = aoc_api.get_node_api(node_file[:node_info], use_secret: false)
101
- return Main.result_status(node_api.oauth_token)
102
- when :node_info
103
- thepath = options.get_next_argument('path')
104
- node_file = aoc_api.resolve_node_file(top_node_file,thepath)
105
- node_api = aoc_api.get_node_api(node_file[:node_info], use_secret: false)
106
- return {type: :single_object,data: {
107
- url: node_file[:node_info]['url'],
108
- username: node_file[:node_info]['access_key'],
109
- password: node_api.oauth_token,
110
- root_id: node_file[:file_id]
111
- }}
112
- when :browse
113
- thepath = options.get_next_argument('path')
114
- node_file = aoc_api.resolve_node_file(top_node_file,thepath)
115
- node_api = aoc_api.get_node_api(node_file[:node_info])
116
- file_info = node_api.read("files/#{node_file[:file_id]}")[:data]
117
- if file_info['type'].eql?('folder')
118
- result = node_api.read("files/#{node_file[:file_id]}/files",options.get_option(:value))
119
- items = result[:data]
120
- self.format.display_status("Items: #{result[:data].length}/#{result[:http]['X-Total-Count']}")
121
- else
122
- items = [file_info]
123
- end
124
- return {type: :object_list,data: items,fields: %w[name type recursive_size size modified_time access_level]}
125
- when :find
126
- thepath = options.get_next_argument('path')
127
- node_file = aoc_api.resolve_node_file(top_node_file,thepath)
128
- test_block = Aspera::Node.file_matcher(options.get_option(:value))
129
- return {type: :object_list,data: aoc_api.find_files(node_file,test_block),fields: ['path']}
130
- when :mkdir
131
- thepath = options.get_next_argument('path')
132
- containing_folder_path = thepath.split(AoC::PATH_SEPARATOR)
133
- new_folder = containing_folder_path.pop
134
- node_file = aoc_api.resolve_node_file(top_node_file,containing_folder_path.join(AoC::PATH_SEPARATOR))
135
- node_api = aoc_api.get_node_api(node_file[:node_info])
136
- result = node_api.create("files/#{node_file[:file_id]}/files",{name: new_folder,type: :folder})[:data]
137
- return Main.result_status("created: #{result['name']} (id=#{result['id']})")
138
- when :rename
139
- thepath = options.get_next_argument('source path')
140
- newname = options.get_next_argument('new name')
141
- node_file = aoc_api.resolve_node_file(top_node_file,thepath)
142
- node_api = aoc_api.get_node_api(node_file[:node_info])
143
- result = node_api.update("files/#{node_file[:file_id]}",{name: newname})[:data]
144
- return Main.result_status("renamed #{thepath} to #{newname}")
145
- when :delete
146
- thepath = options.get_next_argument('path')
147
- return do_bulk_operation(thepath,'deleted',id_result: 'path') do |l_path|
148
- raise "expecting String (path), got #{l_path.class.name} (#{l_path})" unless l_path.is_a?(String)
149
- node_file = aoc_api.resolve_node_file(top_node_file,l_path)
150
- node_api = aoc_api.get_node_api(node_file[:node_info])
151
- result = node_api.delete("files/#{node_file[:file_id]}")[:data]
152
- {'path' => l_path}
153
- end
154
- when :transfer
155
- # client side is agent
156
- # server side is protocol server
157
- # in same workspace
158
- server_home_node_file = client_home_node_file = top_node_file
159
- # default is push
160
- case options.get_option(:operation,is_type: :mandatory)
161
- when :push
162
- client_tr_oper = Fasp::TransferSpec::DIRECTION_SEND
163
- client_folder = options.get_option(:from_folder,is_type: :mandatory)
164
- server_folder = transfer.destination_folder(client_tr_oper)
165
- when :pull
166
- client_tr_oper = Fasp::TransferSpec::DIRECTION_RECEIVE
167
- client_folder = transfer.destination_folder(client_tr_oper)
168
- server_folder = options.get_option(:from_folder,is_type: :mandatory)
169
- end
170
- client_node_file = aoc_api.resolve_node_file(client_home_node_file,client_folder)
171
- server_node_file = aoc_api.resolve_node_file(server_home_node_file,server_folder)
172
- # force node as transfer agent
173
- client_node_api = aoc_api.get_node_api(client_node_file[:node_info], use_secret: false)
174
- @agents[:transfer].agent_instance = Fasp::AgentNode.new({
175
- url: client_node_api.params[:base_url],
176
- username: client_node_file[:node_info]['access_key'],
177
- password: client_node_api.oauth_token,
178
- root_id: client_node_file[:file_id]
179
- })
180
- # additional node to node TS info
181
- add_ts = {
182
- 'remote_access_key' => server_node_file[:node_info]['access_key'],
183
- 'destination_root_id' => server_node_file[:file_id],
184
- 'source_root_id' => client_node_file[:file_id]
185
- }
186
- return Main.result_transfer(transfer_start(AoC::FILES_APP,client_tr_oper,server_node_file,add_ts))
187
- when :upload
188
- node_file = aoc_api.resolve_node_file(top_node_file,transfer.destination_folder(Fasp::TransferSpec::DIRECTION_SEND))
189
- add_ts = {'tags' => {'aspera' => {'files' => {'parentCwd' => "#{node_file[:node_info]['id']}:#{node_file[:file_id]}"}}}}
190
- return Main.result_transfer(transfer_start(AoC::FILES_APP,Fasp::TransferSpec::DIRECTION_SEND,node_file,add_ts))
191
- when :download
192
- source_paths = transfer.ts_source_paths
193
- # special case for AoC : all files must be in same folder
194
- source_folder = source_paths.shift['source']
195
- # if a single file: split into folder and path
196
- if source_paths.empty?
197
- source_folder = source_folder.split(AoC::PATH_SEPARATOR)
198
- source_paths = [{'source' => source_folder.pop}]
199
- source_folder = source_folder.join(AoC::PATH_SEPARATOR)
200
- end
201
- node_file = aoc_api.resolve_node_file(top_node_file,source_folder)
202
- # override paths with just filename
203
- add_ts = {'tags' => {'aspera' => {'files' => {'parentCwd' => "#{node_file[:node_info]['id']}:#{node_file[:file_id]}"}}}}
204
- add_ts['paths'] = source_paths
205
- return Main.result_transfer(transfer_start(AoC::FILES_APP,Fasp::TransferSpec::DIRECTION_RECEIVE,node_file,add_ts))
206
- when :http_node_download
207
- source_paths = transfer.ts_source_paths
208
- source_folder = source_paths.shift['source']
209
- if source_paths.empty?
210
- source_folder = source_folder.split(AoC::PATH_SEPARATOR)
211
- source_paths = [{'source' => source_folder.pop}]
212
- source_folder = source_folder.join(AoC::PATH_SEPARATOR)
213
- end
214
- raise CliBadArgument,'one file at a time only in HTTP mode' if source_paths.length > 1
215
- file_name = source_paths.first['source']
216
- node_file = aoc_api.resolve_node_file(top_node_file,File.join(source_folder,file_name))
217
- node_api = aoc_api.get_node_api(node_file[:node_info])
218
- node_api.call(
219
- operation: 'GET',
220
- subpath: "files/#{node_file[:file_id]}/content",
221
- save_to_file: File.join(transfer.destination_folder(Fasp::TransferSpec::DIRECTION_RECEIVE),file_name))
222
- return Main.result_status("downloaded: #{file_name}")
223
- when :v3
224
- # Note: other common actions are unauthorized with user scope
225
- command_legacy = options.get_next_command(Node::SIMPLE_ACTIONS)
226
- # TODO: shall we support all methods here ? what if there is a link ?
227
- node_api = aoc_api.get_node_api(top_node_file[:node_info])
228
- return Node.new(@agents.merge(skip_basic_auth_options: true, node_api: node_api)).execute_action(command_legacy)
229
- when :file
230
- command_node_file = options.get_next_command(%i[show permission modify])
231
- file_path = options.get_option(:path)
232
- node_file =
233
- if !file_path.nil?
234
- # directly returns node_file info
235
- aoc_api.resolve_node_file(top_node_file,file_path) # TODO: allow follow link ?
236
- else
237
- # build node_file info from next argument on command line
238
- {node_info: top_node_file[:node_info],file_id: instance_identifier}
239
- end
240
- node_api = aoc_api.get_node_api(node_file[:node_info])
241
- case command_node_file
242
- when :show
243
- items = node_api.read("files/#{node_file[:file_id]}")[:data]
244
- return {type: :single_object,data: items}
245
- when :modify
246
- update_param = options.get_next_argument('update data (Hash)')
247
- res = node_api.update("files/#{node_file[:file_id]}",update_param)[:data]
248
- return {type: :single_object,data: res}
249
- when :permission
250
- command_perm = options.get_next_command(%i[list create delete])
251
- case command_perm
252
- when :list
253
- # generic options : TODO: as arg ? option_url_query
254
- list_options ||= {'include' => ['[]','access_level','permission_count']}
255
- # special value: ALL will show all permissions
256
- if !VAL_ALL.eql?(node_file[:file_id])
257
- # add which one to get
258
- list_options['file_id'] = node_file[:file_id]
259
- list_options['inherited'] ||= false
260
- end
261
- items = node_api.read('permissions',list_options)[:data]
262
- return {type: :object_list,data: items}
263
- when :delete
264
- perm_id=instance_identifier
265
- return do_bulk_operation(perm_id,'deleted') do |one_id|
266
- node_api.delete("permissions/#{perm_id}")
267
- {'id' => one_id}
268
- end
269
- #node_api.delete("permissions/#{perm_id}")
270
- when :create
271
- create_param=options.get_next_argument('creation data (Hash)')
272
- #access_id = "#{ID_AK_ADMIN}_WS_#{@workspace_info['id']}"
273
- default_params = {
274
- 'file_id' => node_file[:file_id], # mandatory
275
- #'access_type' => 'user', # mandatory: user or group
276
- #'access_id' => access_id, # id of user or group
277
- 'access_levels' => Aspera::Node::ACCESS_LEVELS,
278
- 'tags' => {'aspera' => {'files' => {'workspace' => {
279
- 'id' => @workspace_info['id'],
280
- 'workspace_name' => @workspace_info['name'],
281
- 'user_name' => aoc_api.user_info['name'],
282
- 'shared_by_user_id' => aoc_api.user_info['id'],
283
- 'shared_by_name' => aoc_api.user_info['name'],
284
- 'shared_by_email' => aoc_api.user_info['email'],
285
- #'shared_with_name' => access_id,
286
- 'access_key' => node_file[:node_info]['access_key'],
287
- 'node' => node_file[:node_info]['name']}}}}}
288
- create_param = default_params.deep_merge(create_param)
289
- if create_param.has_key?('with')
290
- contact_info = aoc_api.lookup_entity_by_name(
291
- 'contacts',
292
- create_param['with'],
293
- {'current_workspace_id' => @workspace_info['id'],'context'=> 'share_folder'})
294
- create_param.delete('with')
295
- create_param['access_type']=contact_info['source_type']
296
- create_param['access_id']=contact_info['source_id']
297
- create_param['tags']['aspera']['files']['workspace']['shared_with_name']=contact_info['email']
298
- end
299
- opt_link_name=nil
300
- if create_param.has_key?('link_name')
301
- opt_link_name=create_param['link_name']
302
- create_param.delete('link_name')
303
- end
304
- # for admin type:
305
- #node_file[:node_info]
306
- #node_api = aoc_api.get_node_api(node_file[:node_info])
307
- created_data = node_api.create('permissions',create_param)[:data]
308
- event_creation={
309
- 'types' => ['permission.created'],
310
- 'node_id' => node_file[:node_info]['id'],
311
- 'workspace_id' => @workspace_info['id'],
312
- 'data' => created_data # Response from previous step
313
- }
314
- #(optional). The name of the folder to be displayed to the destination user. Use it if its value is different from the "share_as" field.
315
- event_creation['link_name']=opt_link_name unless opt_link_name.nil?
316
- aoc_api.create('events',event_creation)
317
- return { type: :single_object, data: created_data}
318
- else raise "internal error:shall not reach here (#{command_perm})"
319
- end
320
- else raise "internal error:shall not reach here (#{command_node_file})"
321
- end
322
- end # command_repo
323
- raise 'internal error:shall not reach here'
324
- end # execute_node_gen4_command
325
- AOC_PARAMS_COPY=%i[link url auth client_id client_secret scope redirect_uri private_key passphrase username password].freeze
326
- # build constructor option list for AoC based on options of CLI
327
- def aoc_params(subpath)
328
- # copy command line options to args
329
- opt = AOC_PARAMS_COPY.each_with_object({}){|i,m|m[i] = options.get_option(i)}
330
- opt[:subpath] = subpath
331
- return opt
98
+ return @cache_api_aoc
332
99
  end
333
100
 
334
- # initialize apis and authentication
335
- # set:
336
- # @workspace_info
337
- # @persist_ids
338
- # returns nil
339
- def set_workspace_info
340
- @url_token_data = aoc_api.url_token_data
341
- if @url_token_data.nil?
342
- default_workspace_id = aoc_api.user_info['default_workspace_id']
343
- @persist_ids = [aoc_api.user_info['id']]
101
+ # @return [Hash] current workspace information,
102
+ def current_workspace_info
103
+ return @cache_workspace_info unless @cache_workspace_info.nil?
104
+ default_workspace_id = if aoc_api.url_token_data.nil?
105
+ aoc_api.current_user_info['default_workspace_id']
344
106
  else
345
- default_workspace_id = @url_token_data['data']['workspace_id']
346
- @persist_ids = [] # TODO : @url_token_data['id'] ?
107
+ aoc_api.url_token_data['data']['workspace_id']
347
108
  end
348
109
 
349
110
  ws_name = options.get_option(:workspace)
@@ -351,42 +112,42 @@ module Aspera
351
112
  case ws_name
352
113
  when :default
353
114
  Log.log.debug('Using default workspace'.green)
354
- raise CliError,'No default workspace defined for user, please specify workspace' if default_workspace_id.nil?
115
+ raise CliError, 'No default workspace defined for user, please specify workspace' if default_workspace_id.nil?
355
116
  default_workspace_id
356
- when String then aoc_api.lookup_entity_by_name('workspaces',ws_name)['id']
117
+ when String then aoc_api.lookup_entity_by_name('workspaces', ws_name)['id']
357
118
  when NilClass then nil
358
- else raise CliError,'unexpected value type for workspace'
119
+ else raise CliError, 'unexpected value type for workspace'
359
120
  end
360
- @workspace_info =
121
+ @cache_workspace_info =
361
122
  begin
362
123
  aoc_api.read("workspaces/#{ws_id}")[:data]
363
124
  rescue Aspera::RestCallError => e
364
125
  Log.log.debug(e.message)
365
126
  { 'id' => :undefined, 'name' => :undefined }
366
127
  end
367
- Log.dump(:workspace_data,@workspace_info)
128
+ Log.dump(:current_workspace_info, @cache_workspace_info)
368
129
  # display workspace
369
- self.format.display_status("Current Workspace: #{@workspace_info['name'].to_s.red}#{@workspace_info['id'] == default_workspace_id ? ' (default)' : ''}")
370
- return nil
130
+ default_flag = @cache_workspace_info['id'] == default_workspace_id ? ' (default)' : ''
131
+ formatter.display_status("Current Workspace: #{@cache_workspace_info['name'].to_s.red}#{default_flag}")
132
+ return @cache_workspace_info
371
133
  end
372
134
 
373
- # @home_node_file (hash with :node_info and :file_id)
374
- def set_home_node_file
375
- if !@url_token_data.nil?
135
+ # @return [Hash] with :node_id and :file_id
136
+ def home_info
137
+ return @cache_home_node_file unless @cache_home_node_file.nil?
138
+ if !aoc_api.url_token_data.nil?
376
139
  assert_public_link_types(['view_shared_file'])
377
- home_node_id = @url_token_data['data']['node_id']
378
- home_file_id = @url_token_data['data']['file_id']
140
+ home_node_id = aoc_api.url_token_data['data']['node_id']
141
+ home_file_id = aoc_api.url_token_data['data']['file_id']
379
142
  end
380
- home_node_id ||= @workspace_info['home_node_id'] || @workspace_info['node_id']
381
- home_file_id ||= @workspace_info['home_file_id']
143
+ home_node_id ||= current_workspace_info['home_node_id'] || current_workspace_info['node_id']
144
+ home_file_id ||= current_workspace_info['home_file_id']
382
145
  raise "Cannot get user's home node id, check your default workspace or specify one" if home_node_id.to_s.empty?
383
- @home_node_file = {
384
- node_info: aoc_api.read("nodes/#{home_node_id}")[:data],
385
- file_id: home_file_id
146
+ @cache_home_node_file = {
147
+ node_id: home_node_id,
148
+ file_id: home_file_id
386
149
  }
387
- aoc_api.check_get_node_file(@home_node_file)
388
-
389
- return nil
150
+ return @cache_home_node_file
390
151
  end
391
152
 
392
153
  # get identifier or name from command line
@@ -396,11 +157,11 @@ module Aspera
396
157
  l_res_name = options.get_option(:name)
397
158
  raise 'Provide either option id or name, not both' unless l_res_id.nil? || l_res_name.nil?
398
159
  # try to find item by name (single partial match or exact match)
399
- l_res_id = aoc_api.lookup_entity_by_name(resource_class_path,l_res_name)['id'] unless l_res_name.nil?
160
+ l_res_id = aoc_api.lookup_entity_by_name(resource_class_path, l_res_name)['id'] unless l_res_name.nil?
400
161
  # if no name or id option, taken on command line (after command)
401
162
  if l_res_id.nil?
402
163
  l_res_id = options.get_next_argument('identifier')
403
- l_res_id = aoc_api.lookup_entity_by_name(resource_class_path,options.get_next_argument('identifier'))['id'] if l_res_id.eql?('name')
164
+ l_res_id = aoc_api.lookup_entity_by_name(resource_class_path, options.get_next_argument('identifier'))['id'] if l_res_id.eql?(ENTITY_NAME_SPECIFIER)
404
165
  end
405
166
  return l_res_id
406
167
  end
@@ -409,129 +170,18 @@ module Aspera
409
170
  return "#{resource_class_path}/#{get_resource_id_from_args(resource_class_path)}"
410
171
  end
411
172
 
412
- # Normalize package creation recipient lists as expected by AoC API
413
- # AoC expects {type: , id: }, but ascli allows providing either the native values or just a name
414
- # in that case, the name is resolved and replaced with {type: , id: }
415
- # @param package_data The whole package creation payload
416
- # @param recipient_list_field The field in structure, i.e. recipients or bcc_recipients
417
- # @return nil package_data is modified
418
- def resolve_package_recipients(package_data,recipient_list_field)
419
- return unless package_data.has_key?(recipient_list_field)
420
- raise CliBadArgument,"#{recipient_list_field} must be an Array" unless package_data[recipient_list_field].is_a?(Array)
421
- new_user_option = options.get_option(:new_user_option,is_type: :mandatory)
422
- # list with resolved elements
423
- resolved_list = []
424
- package_data[recipient_list_field].each do |short_recipient_info|
425
- case short_recipient_info
426
- when Hash # native API information, check keys
427
- raise "#{recipient_list_field} element shall have fields: id and type" unless short_recipient_info.keys.sort.eql?(%w[id type])
428
- when String # CLI helper: need to resolve provided name to type/id
429
- # email: user, else dropbox
430
- entity_type = short_recipient_info.include?('@') ? 'contacts' : 'dropboxes'
431
- begin
432
- full_recipient_info = aoc_api.lookup_entity_by_name(entity_type,short_recipient_info,{'current_workspace_id' => @workspace_info['id']})
433
- rescue RuntimeError => e
434
- raise e unless e.message.start_with?(Aspera::AoC::ENTITY_NOT_FOUND)
435
- # dropboxes cannot be created on the fly
436
- raise "no such shared inbox in workspace #{@workspace_info['name']}" if entity_type.eql?('dropboxes')
437
- # unknown user: create it as external user
438
- full_recipient_info = aoc_api.create('contacts',{
439
- 'current_workspace_id' => @workspace_info['id'],
440
- 'email' => short_recipient_info}.merge(new_user_option))[:data]
441
- end
442
- short_recipient_info = if entity_type.eql?('dropboxes')
443
- {'id' => full_recipient_info['id'],'type' => 'dropbox'}
444
- else
445
- {'id' => full_recipient_info['source_id'],'type' => full_recipient_info['source_type']}
446
- end
447
- else # unexpected extended value, must be String or Hash
448
- raise "#{recipient_list_field} item must be a String (email, shared inbox) or Hash (id,type)"
449
- end # type of recipient info
450
- # add original or resolved recipient info
451
- resolved_list.push(short_recipient_info)
452
- end
453
- # replace with resolved elements
454
- package_data[recipient_list_field] = resolved_list
455
- return nil
456
- end
457
-
458
- def normalize_metadata(pkg_data)
459
- case pkg_data['metadata']
460
- when Array,NilClass # no action
461
- when Hash
462
- api_meta = []
463
- pkg_data['metadata'].each do |k,v|
464
- api_meta.push({
465
- #'input_type' => 'single-dropdown',
466
- 'name' => k,
467
- 'values' => v.is_a?(Array) ? v : [v]
468
- })
469
- end
470
- pkg_data['metadata'] = api_meta
471
- else raise "metadata field if not of expected type: #{pkg_meta.class}"
472
- end
473
- return nil
474
- end
475
-
476
- # Check metadata: remove when validation is done server side
477
- def validate_metadata(pkg_data)
478
- # validate only for shared inboxes
479
- return unless
480
- pkg_data['recipients'].is_a?(Array) &&
481
- pkg_data['recipients'].first.is_a?(Hash) &&
482
- pkg_data['recipients'].first.has_key?('type') &&
483
- pkg_data['recipients'].first['type'].eql?('dropbox')
484
-
485
- shbx_kid = pkg_data['recipients'].first['id']
486
- meta_schema = aoc_api.read("dropboxes/#{shbx_kid}")[:data]['metadata_schema']
487
- if meta_schema.nil? || meta_schema.empty?
488
- Log.log.debug('no metadata in shared inbox')
489
- return
490
- end
491
- pkg_meta = pkg_data['metadata']
492
- raise "package requires metadata: #{meta_schema}" unless pkg_data.has_key?('metadata')
493
- raise 'metadata must be an Array' unless pkg_meta.is_a?(Array)
494
- Log.dump(:metadata,pkg_meta)
495
- pkg_meta.each do |field|
496
- raise 'metadata field must be Hash' unless field.is_a?(Hash)
497
- raise 'metadata field must have name' unless field.has_key?('name')
498
- raise 'metadata field must have values' unless field.has_key?('values')
499
- raise 'metadata values must be an Array' unless field['values'].is_a?(Array)
500
- raise "unknown metadata field: #{field['name']}" if meta_schema.select{|i|i['name'].eql?(field['name'])}.empty?
501
- end
502
- meta_schema.each do |field|
503
- provided=pkg_meta.select{|i|i['name'].eql?(field['name'])}
504
- raise "only one field with name #{field['name']} allowed" if provided.count > 1
505
- raise "missing mandatory field: #{field['name']}" if field['required'] && provided.empty?
506
- end
507
- end
508
-
509
- # private
510
- def option_url_query(default)
511
- query = options.get_option(:query)
512
- query = default if query.nil?
513
- Log.log.debug("Query=#{query}".bg_red)
514
- begin
515
- # check it is suitable
516
- URI.encode_www_form(query) unless query.nil?
517
- rescue StandardError => e
518
- raise CliBadArgument,"query must be an extended value which can be encoded with URI.encode_www_form. Refer to manual. (#{e.message})"
519
- end
520
- return query
521
- end
522
-
523
173
  def assert_public_link_types(expected)
524
- raise CliBadArgument,"public link type is #{@url_token_data['purpose']} but action requires one of #{expected.join(',')}" \
525
- unless expected.include?(@url_token_data['purpose'])
174
+ raise CliBadArgument, "public link type is #{aoc_api.url_token_data['purpose']} but action requires one of #{expected.join(',')}" \
175
+ unless expected.include?(aoc_api.url_token_data['purpose'])
526
176
  end
527
177
 
528
178
  # Call aoc_api.read with same parameters.
529
179
  # Use paging if necessary to get all results
530
- # @return {list: , total: }
531
- def read_with_paging(resource_class_path,base_query)
180
+ # @return [Hash] {list: , total: }
181
+ def read_with_paging(resource_class_path, base_query)
532
182
  raise 'Query must be Hash' unless base_query.is_a?(Hash)
533
183
  # set default large page if user does not specify own parameters. AoC Caps to 1000 anyway
534
- base_query['per_page'] = 1000 unless base_query.has_key?('per_page')
184
+ base_query['per_page'] = 1000 unless base_query.key?('per_page')
535
185
  max_items = base_query[MAX_ITEMS]
536
186
  base_query.delete(MAX_ITEMS)
537
187
  max_pages = base_query[MAX_PAGES]
@@ -544,7 +194,7 @@ module Aspera
544
194
  loop do
545
195
  query = base_query.clone
546
196
  query['page'] = current_page
547
- result = aoc_api.read(resource_class_path,query)
197
+ result = aoc_api.read(resource_class_path, query)
548
198
  total_count = result[:http]['X-Total-Count']
549
199
  page_count += 1
550
200
  current_page += 1
@@ -555,12 +205,66 @@ module Aspera
555
205
  break if !max_pages.nil? && page_count > max_pages
556
206
  break if !max_items.nil? && item_list.count > max_items
557
207
  end
558
- return {list: item_list,total: total_count}
208
+ return {list: item_list, total: total_count}
559
209
  end
560
210
 
211
+ NODE4_EXT_COMMANDS = %i[transfer].concat(Node::COMMANDS_GEN4).freeze
212
+ private_constant :NODE4_EXT_COMMANDS
213
+
214
+ # @param file_id [String] root file id for the operation (can be AK root, or other, e.g. package, or link)
215
+ # @param scope [String] node scope, or nil (admin)
216
+ def execute_nodegen4_command(command_repo, node_id, file_id: nil, scope: nil)
217
+ top_node_api = aoc_api.node_api_from(node_id: node_id, workspace_info: current_workspace_info, scope: scope)
218
+ 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?
219
+ node_plugin = Node.new(@agents.merge(
220
+ skip_basic_auth_options: true,
221
+ skip_node_options: true,
222
+ node_api: top_node_api))
223
+ case command_repo
224
+ when *Node::COMMANDS_GEN4
225
+ return node_plugin.execute_command_gen4(command_repo, file_id)
226
+ when :transfer
227
+ # client side is agent
228
+ # server side is protocol server
229
+ # in same workspace
230
+ # default is push
231
+ case options.get_option(:operation, is_type: :mandatory)
232
+ when :push
233
+ client_direction = Fasp::TransferSpec::DIRECTION_SEND
234
+ client_folder = options.get_option(:from_folder, is_type: :mandatory)
235
+ server_folder = transfer.destination_folder(client_direction)
236
+ when :pull
237
+ client_direction = Fasp::TransferSpec::DIRECTION_RECEIVE
238
+ client_folder = transfer.destination_folder(client_direction)
239
+ server_folder = options.get_option(:from_folder, is_type: :mandatory)
240
+ end
241
+ client_apfid = top_node_api.resolve_api_fid(file_id, client_folder)
242
+ server_apfid = top_node_api.resolve_api_fid(file_id, server_folder)
243
+ # force node as transfer agent
244
+ @agents[:transfer].agent_instance = Fasp::AgentNode.new({
245
+ url: client_apfid[:api].params[:base_url],
246
+ username: client_apfid[:api].app_info[:node_info]['access_key'],
247
+ password: client_apfid[:api].oauth_token,
248
+ root_id: client_apfid[:file_id]
249
+ })
250
+ # additional node to node TS info
251
+ add_ts = {
252
+ 'remote_access_key' => server_apfid[:api].app_info[:node_info]['access_key'],
253
+ 'destination_root_id' => server_apfid[:file_id],
254
+ 'source_root_id' => client_apfid[:file_id]
255
+ }
256
+ return Main.result_transfer(transfer.start(server_apfid[:api].transfer_spec_gen4(
257
+ server_apfid[:file_id],
258
+ client_direction,
259
+ add_ts)))
260
+ else raise "INTERNAL ERROR: Missing case: #{command_repo}"
261
+ end # command_repo
262
+ # raise 'internal error:shall not reach here'
263
+ end # execute_nodegen4_command
264
+
561
265
  def execute_admin_action
562
266
  # upgrade scope to admin
563
- aoc_api.oauth.gparams[:scope] = AoC::SCOPE_FILES_ADMIN
267
+ aoc_api.oauth.generic_parameters[:scope] = AoC::SCOPE_FILES_ADMIN
564
268
  command_admin = options.get_next_command(%i[ats resource usage_reports analytics subscription auth_providers])
565
269
  case command_admin
566
270
  when :auth_providers
@@ -568,7 +272,7 @@ module Aspera
568
272
  case command_auth_prov
569
273
  when :list
570
274
  providers = aoc_api.read('admin/auth_providers')[:data]
571
- return {type: :object_list,data: providers}
275
+ return {type: :object_list, data: providers}
572
276
  when :update
573
277
  raise 'not implemented'
574
278
  end
@@ -623,69 +327,71 @@ module Aspera
623
327
  }
624
328
  }
625
329
  "
626
- result = bss_api.create('graphql',{'variables' => {'organization_id' => org['id']},'query' => graphql_query})[:data]['data']
627
- return {type: :single_object,data: result['aoc']['bssSubscription']}
330
+ result = bss_api.create('graphql', {'variables' => {'organization_id' => org['id']}, 'query' => graphql_query})[:data]['data']
331
+ return {type: :single_object, data: result['aoc']['bssSubscription']}
628
332
  when :ats
629
333
  ats_api = Rest.new(aoc_api.params.deep_merge({
630
334
  base_url: aoc_api.params[:base_url] + '/admin/ats/pub/v1',
631
335
  auth: {scope: AoC::SCOPE_FILES_ADMIN_USER}
632
336
  }))
633
- return Ats.new(@agents).execute_action_gen(ats_api)
337
+ return Ats.new(@agents.merge(skip_node_options: true)).execute_action_gen(ats_api)
634
338
  when :analytics
635
339
  analytics_api = Rest.new(aoc_api.params.deep_merge({
636
- base_url: aoc_api.params[:base_url].gsub('/api/v1','') + '/analytics/v2',
340
+ base_url: aoc_api.params[:base_url].gsub('/api/v1', '') + '/analytics/v2',
637
341
  auth: {scope: AoC::SCOPE_FILES_ADMIN_USER}
638
342
  }))
639
343
  command_analytics = options.get_next_command(%i[application_events transfers])
640
344
  case command_analytics
641
345
  when :application_events
642
346
  event_type = command_analytics.to_s
643
- events = analytics_api.read("organizations/#{aoc_api.user_info['organization_id']}/#{event_type}")[:data][event_type]
644
- return {type: :object_list,data: events}
347
+ events = analytics_api.read("organizations/#{aoc_api.current_user_info['organization_id']}/#{event_type}")[:data][event_type]
348
+ return {type: :object_list, data: events}
645
349
  when :transfers
646
350
  event_type = command_analytics.to_s
647
351
  filter_resource = options.get_option(:name) || 'organizations'
648
352
  filter_id = options.get_option(:id) ||
649
- case filter_resource
650
- when 'organizations' then aoc_api.user_info['organization_id']
651
- when 'users' then aoc_api.user_info['id']
652
- when 'nodes' then aoc_api.user_info['id'] # TODO: consistent ? # rubocop:disable Lint/DuplicateBranch
653
- else raise 'organizations or users for option --name'
654
- end
353
+ case filter_resource
354
+ when 'organizations' then aoc_api.current_user_info['organization_id']
355
+ when 'users' then aoc_api.current_user_info['id']
356
+ when 'nodes' then aoc_api.current_user_info['id'] # TODO: consistent ? # rubocop:disable Lint/DuplicateBranch
357
+ else raise 'organizations or users for option --name'
358
+ end
655
359
  filter = options.get_option(:query) || {}
656
360
  raise 'query must be Hash' unless filter.is_a?(Hash)
657
361
  filter['limit'] ||= 100
658
- if options.get_option(:once_only,is_type: :mandatory)
362
+ if options.get_option(:once_only, is_type: :mandatory)
659
363
  saved_date = []
660
- startdate_persistency = PersistencyActionOnce.new(
364
+ start_date_persistency = PersistencyActionOnce.new(
661
365
  manager: @agents[:persistency],
662
366
  data: saved_date,
663
- ids: IdGenerator.from_list(['aoc_ana_date',options.get_option(:url,is_type: :mandatory),@workspace_info['name']].push(filter_resource,filter_id)))
664
- start_datetime = saved_date.first
665
- stop_datetime = Time.now.utc.strftime('%FT%T.%LZ')
666
- #Log.log().error("start: #{start_datetime}")
667
- #Log.log().error("end: #{stop_datetime}")
668
- saved_date[0] = stop_datetime
669
- filter['start_time'] = start_datetime unless start_datetime.nil?
670
- filter['stop_time'] = stop_datetime
367
+ ids: IdGenerator.from_list(['aoc_ana_date', options.get_option(:url, is_type: :mandatory), current_workspace_info['name']].push(
368
+ filter_resource,
369
+ filter_id)))
370
+ start_date_time = saved_date.first
371
+ stop_date_time = Time.now.utc.strftime('%FT%T.%LZ')
372
+ # Log.log().error("start: #{start_date_time}")
373
+ # Log.log().error("end: #{stop_date_time}")
374
+ saved_date[0] = stop_date_time
375
+ filter['start_time'] = start_date_time unless start_date_time.nil?
376
+ filter['stop_time'] = stop_date_time
671
377
  end
672
- events = analytics_api.read("#{filter_resource}/#{filter_id}/#{event_type}",option_url_query(filter))[:data][event_type]
673
- startdate_persistency&.save
378
+ events = analytics_api.read("#{filter_resource}/#{filter_id}/#{event_type}", option_url_query(filter))[:data][event_type]
379
+ start_date_persistency&.save
674
380
  if !options.get_option(:notif_to).nil?
675
381
  events.each do |tr_event|
676
382
  config.send_email_template(values: {ev: tr_event})
677
383
  end
678
384
  end
679
- return {type: :object_list,data: events}
385
+ return {type: :object_list, data: events}
680
386
  end
681
387
  when :resource
682
- resource_type = options.get_next_argument('resource',expected: KNOWN_AOC_RES)
388
+ resource_type = options.get_next_argument('resource', expected: KNOWN_AOC_RES)
683
389
  # get path on API, resource type is singular, but api is plural
684
390
  resource_class_path =
685
391
  case resource_type
686
392
  # special cases: singleton, in admin, with x
687
- when :self,:organization then resource_type
688
- when :client_registration_token,:client_access_key then "admin/#{resource_type}s"
393
+ when :self, :organization then resource_type
394
+ when :client_registration_token, :client_access_key then "admin/#{resource_type}s"
689
395
  when :application then 'admin/apps_new'
690
396
  when :dropbox then resource_type.to_s + 'es'
691
397
  when :kms_profile then "integrations/#{resource_type}s"
@@ -695,10 +401,9 @@ module Aspera
695
401
  singleton_object = %i[self organization].include?(resource_type)
696
402
  global_operations = %i[create list]
697
403
  supported_operations = %i[show modify]
698
- supported_operations.push(:delete,*global_operations) unless singleton_object
699
- supported_operations.push(:v4,:v3) if resource_type.eql?(:node)
404
+ supported_operations.push(:delete, *global_operations) unless singleton_object
405
+ supported_operations.push(:do) if resource_type.eql?(:node)
700
406
  supported_operations.push(:set_pub_key) if resource_type.eql?(:client)
701
- supported_operations.push(:shared_folder) if resource_type.eql?(:workspace)
702
407
  command = options.get_next_command(supported_operations)
703
408
  # require identifier for non global commands
704
409
  if !singleton_object && !global_operations.include?(command)
@@ -712,42 +417,43 @@ module Aspera
712
417
  id_result = 'token' if resource_class_path.eql?('admin/client_registration_tokens')
713
418
  # TODO: report inconsistency: creation url is !=, and does not return id.
714
419
  resource_class_path = 'admin/client_registration/token' if resource_class_path.eql?('admin/client_registration_tokens')
715
- list_or_one = options.get_next_argument('creation data (Hash)')
716
- return do_bulk_operation(list_or_one,'created',id_result: id_result) do |params|
420
+ list_or_one = options.get_next_argument('creation data', type: Hash)
421
+ return do_bulk_operation(list_or_one, 'created', id_result: id_result) do |params|
717
422
  raise 'expecting Hash' unless params.is_a?(Hash)
718
- aoc_api.create(resource_class_path,params)[:data]
423
+ aoc_api.create(resource_class_path, params)[:data]
719
424
  end
720
425
  when :list
721
426
  default_fields = ['id']
722
427
  default_query = {}
723
428
  case resource_type
724
- when :application then default_query = {organization_apps: true};
725
- default_fields.push('app_type','app_name','available','direct_authorizations_allowed','workspace_authorizations_allowed')
726
- when :client,:client_access_key,:dropbox,:group,:package,:saml_configuration,:workspace then default_fields.push('name')
727
- when :client_registration_token then default_fields.push('value','data.client_subject_scopes','created_at')
429
+ when :application
430
+ default_query = {organization_apps: true}
431
+ default_fields.push('app_type', 'app_name', 'available', 'direct_authorizations_allowed', 'workspace_authorizations_allowed')
432
+ when :client, :client_access_key, :dropbox, :group, :package, :saml_configuration, :workspace then default_fields.push('name')
433
+ when :client_registration_token then default_fields.push('value', 'data.client_subject_scopes', 'created_at')
728
434
  when :contact then default_fields = %w[email name source_id source_type]
729
- when :node then default_fields.push('name','host','access_key')
435
+ when :node then default_fields.push('name', 'host', 'access_key')
730
436
  when :operation then default_fields = nil
731
- when :short_link then default_fields.push('short_url','data.url_token_data.purpose')
732
- when :user then default_fields.push('name','email')
437
+ when :short_link then default_fields.push('short_url', 'data.url_token_data.purpose')
438
+ when :user then default_fields.push('name', 'email')
733
439
  when :group_membership then default_fields.push(*%w[group_id member_type member_id])
734
440
  when :workspace_membership then default_fields.push(*%w[workspace_id member_type member_id])
735
441
  end
736
- items = read_with_paging(resource_class_path,option_url_query(default_query))
442
+ items = read_with_paging(resource_class_path, option_url_query(default_query))
737
443
  count_msg = "Items: #{items[:list].length}/#{items[:total]}"
738
444
  count_msg = count_msg.bg_red unless items[:list].length.eql?(items[:total].to_i)
739
- self.format.display_status(count_msg)
740
- return {type: :object_list,data: items[:list],fields: default_fields}
445
+ formatter.display_status(count_msg)
446
+ return {type: :object_list, data: items[:list], fields: default_fields}
741
447
  when :show
742
448
  object = aoc_api.read(resource_instance_path)[:data]
743
449
  fields = object.keys.reject{|k|k.eql?('certificate')}
744
450
  return { type: :single_object, data: object, fields: fields }
745
451
  when :modify
746
452
  changes = options.get_next_argument('modified parameters (hash)')
747
- aoc_api.update(resource_instance_path,changes)
453
+ aoc_api.update(resource_instance_path, changes)
748
454
  return Main.result_status('modified')
749
455
  when :delete
750
- return do_bulk_operation(res_id,'deleted') do |one_id|
456
+ return do_bulk_operation(res_id, 'deleted') do |one_id|
751
457
  aoc_api.delete("#{resource_class_path}/#{one_id}")
752
458
  {'id' => one_id}
753
459
  end
@@ -755,109 +461,15 @@ module Aspera
755
461
  # special : reads private and generate public
756
462
  the_private_key = options.get_next_argument('private_key')
757
463
  the_public_key = OpenSSL::PKey::RSA.new(the_private_key).public_key.to_s
758
- aoc_api.update(resource_instance_path,{jwt_grant_enabled: true, public_key: the_public_key})
464
+ aoc_api.update(resource_instance_path, {jwt_grant_enabled: true, public_key: the_public_key})
759
465
  return Main.result_success
760
- when :v3,:v4
761
- res_data = aoc_api.read(resource_instance_path)[:data]
762
- api_node = aoc_api.get_node_api(res_data)
763
- return Node.new(@agents.merge(skip_basic_auth_options: true, node_api: api_node)).execute_action if command.eql?(:v3)
764
- ak_root_file_id = api_node.read("access_keys/#{res_data['access_key']}")[:data]['root_file_id']
765
- command_repo = options.get_next_command(NODE4_COMMANDS)
766
- return execute_node_gen4_command(command_repo,{node_info: res_data, file_id: ak_root_file_id})
767
- when :shared_folder
768
- Log.log.warn('ATTENTION: alpha, under development, do not use')
769
- # inside a workspace
770
- command_shared = options.get_next_command(%i[list create delete]) # member
771
- core_api=Rest.new(aoc_api.params.merge(base_url: aoc_api.params[:base_url].gsub('/api.','/sedemo.')))
772
- # generic permission created for each shared folder
773
- access_id = "#{ID_AK_ADMIN}_WS_#{res_id}"
774
- case command_shared
775
- when :list
776
- query=options.get_option(:query)
777
- query={'admin' => true, 'access_id' => access_id, 'access_type' => 'user'} if query.nil?
778
- res_data = aoc_api.read("#{resource_instance_path}/permissions",query)[:data]
779
- return { type: :object_list, data: res_data, fields: %w[id node_id file_id node_name file.path tags.aspera.files.workspace.share_as access_id]}
780
- when :member
781
- #https://sedemo.ibmaspera.com/api/v1/node/8669/permissions_and_members/3270?inherited=false&aspera-node-basic=8669&admin=true&page=1&per_page=25
782
- when :delete
783
- shared_id=instance_identifier
784
- all_shared=aoc_api.read("#{resource_instance_path}/permissions",query)[:data].select{|i|i['id'].eql?(shared_id)}
785
- raise 'no such shared folder id' if all_shared.empty?
786
- raise 'error' unless all_shared.length.eql?(1)
787
- shared_info=all_shared.first
788
- #return { type: :single_object, data: shared_info}
789
- node_id=shared_info['node_id']
790
- Log.log.warn('ATTENTION: under dev: user vars: V1 and V2')
791
- core_api.call(
792
- operation: 'DELETE',
793
- subpath: "node/#{node_id}/permissions",
794
- headers: {
795
- 'Accept' => 'application/json',
796
- 'aspera-node-auth' => ENV['V1'],
797
- 'aspera-node-tokens' => ENV['V2']
798
- },
799
- url_params: {
800
- 'ids' => shared_id,
801
- 'aspera-node-basic' => node_id,
802
- 'aspera-node-prefer-basic' => node_id
803
- }
804
- )
805
- return Main.result_success
806
- when :create
807
- # workspace information
808
- ws_info = aoc_api.read(resource_instance_path)[:data]
809
- shared_create_data = options.get_next_argument('creation data',type: Hash)
810
- # node is either provided by user, or by default the one of workspace
811
- node_id = shared_create_data.has_key?('node_id') ? shared_create_data['node_id'] : ws_info['node_id']
812
- # remove from creation data if present, as it is not a standard argument
813
- shared_create_data.delete('node_id')
814
- # get optional link_name
815
- #opt_link_name=shared_create_data['link_name']
816
- shared_create_data.delete('link_name')
817
- raise 'missing node information: path' unless shared_create_data.has_key?('path')
818
- folder_path=shared_create_data['path']
819
- shared_create_data.delete('path')
820
- node_file={node_info: aoc_api.read("nodes/#{node_id}")[:data], file_id: 1}
821
- node_file = aoc_api.resolve_node_file(node_file,folder_path)
822
- node_api = aoc_api.get_node_api(node_file[:node_info])
823
- access_id = "#{ID_AK_ADMIN}_WS_#{ws_info['id']}"
824
- # use can specify: tags.aspera.files.workspace.share_as to File.basename(folder_path)
825
- default_create_data = {
826
- 'file_id' => node_file[:file_id],
827
- 'access_type' => 'user',
828
- 'access_id' => access_id,
829
- 'access_levels' => %w[list read write delete mkdir rename preview],
830
- 'tags' => {'aspera' => {'files' => {'workspace' => {
831
- 'id' => ws_info['id'],
832
- 'workspace_name' => ws_info['name'],
833
- 'user_name' => aoc_api.user_info['name'],
834
- 'shared_by_user_id' => aoc_api.user_info['id'],
835
- 'shared_by_name' => aoc_api.user_info['name'],
836
- 'shared_by_email' => aoc_api.user_info['email'],
837
- 'shared_with_name' => access_id,
838
- 'access_key' => node_file[:node_info]['access_key'],
839
- 'node' => node_file[:node_info]['name']}
840
- }}}}
841
- shared_create_data = default_create_data.deep_merge(default_create_data) # ?aspera-node-basic=#{node_id}&aspera-node-prefer-basic=#{node_id}
842
- created_data = node_api.create('permissions',shared_create_data)[:data]
843
- # new API:
844
- #created_data=aoc_api.create("node/#{node_id}/permissions",shared_create_data)[:data]
845
- # TODO: send event
846
- event_creation={
847
- 'types' => ['permission.created'],
848
- 'node_id' => node_file[:node_info]['id'],
849
- 'workspace_id' => ws_info['id'],
850
- 'data' => created_data # Response from previous step
851
- }
852
- #(optional). The name of the folder to be displayed to the destination user. Use it if its value is different from the "share_as" field.
853
- #event_creation['link_name']=opt_link_name unless opt_link_name.nil?
854
- aoc_api.create('events',event_creation)
855
- return { type: :single_object, data: created_data}
856
- end
466
+ when :do
467
+ command_repo = options.get_next_command(NODE4_EXT_COMMANDS)
468
+ return execute_nodegen4_command(command_repo, res_id)
857
469
  else raise 'unknown command'
858
470
  end
859
471
  when :usage_reports
860
- return {type: :object_list,data: aoc_api.read('usage_reports',{workspace_id: @workspace_info['id']})[:data]}
472
+ return {type: :object_list, data: aoc_api.read('usage_reports', {workspace_id: current_workspace_info['id']})[:data]}
861
473
  end
862
474
  end
863
475
 
@@ -869,13 +481,13 @@ module Aspera
869
481
  case command
870
482
  when :reminder
871
483
  # send an email reminder with list of orgs
872
- user_email = options.get_option(:username,is_type: :mandatory)
873
- Rest.new(base_url: "#{AoC.api_base_url}/#{AoC::API_V1}").create('organization_reminders',{email: user_email})[:data]
484
+ user_email = options.get_option(:username, is_type: :mandatory)
485
+ Rest.new(base_url: "#{AoC.api_base_url}/#{AoC::API_V1}").create('organization_reminders', {email: user_email})[:data]
874
486
  return Main.result_status("List of organizations user is member of, has been sent by e-mail to #{user_email}")
875
487
  when :servers
876
- return {type: :object_list,data: Rest.new(base_url: "#{AoC.api_base_url}/#{AoC::API_V1}").read('servers')[:data]}
488
+ return {type: :object_list, data: Rest.new(base_url: "#{AoC.api_base_url}/#{AoC::API_V1}").read('servers')[:data]}
877
489
  when :bearer_token
878
- return {type: :text,data: aoc_api.oauth_token}
490
+ return {type: :text, data: aoc_api.oauth_token}
879
491
  when :organization
880
492
  return { type: :single_object, data: aoc_api.read('organization')[:data] }
881
493
  when :tier_restrictions
@@ -887,116 +499,103 @@ module Aspera
887
499
  when :workspaces
888
500
  case options.get_next_command(%i[list current])
889
501
  when :list
890
- return {type: :object_list,data: aoc_api.read('workspaces')[:data],fields: %w[id name]}
502
+ return {type: :object_list, data: aoc_api.read('workspaces')[:data], fields: %w[id name]}
891
503
  when :current
892
- set_workspace_info
893
- return { type: :single_object, data: @workspace_info }
504
+ return { type: :single_object, data: current_workspace_info }
894
505
  end
895
506
  when :profile
896
507
  case options.get_next_command(%i[show modify])
897
508
  when :show
898
- return { type: :single_object, data: aoc_api.user_info }
509
+ return { type: :single_object, data: aoc_api.current_user_info(exception: true) }
899
510
  when :modify
900
- aoc_api.update("users/#{aoc_api.user_info['id']}",options.get_next_argument('modified parameters (hash)'))
511
+ aoc_api.update("users/#{aoc_api.current_user_info(exception: true)['id']}", options.get_next_argument('modified parameters (hash)'))
901
512
  return Main.result_status('modified')
902
513
  end
903
514
  end
904
515
  when :packages
905
- set_workspace_info if @url_token_data.nil?
906
- package_command = options.get_next_command([%i[shared_inboxes send recv list show delete],NODE4_CMD_PATH].flatten)
516
+ package_command = options.get_next_command(%i[shared_inboxes send recv list show delete].concat(Node::NODE4_READ_ACTIONS))
907
517
  case package_command
908
518
  when :shared_inboxes
909
519
  case options.get_next_command(%i[list show])
910
520
  when :list
911
521
  query = option_url_query(nil)
912
522
  if query.nil?
913
- query = {'embed[]' => 'dropbox','aggregate_permissions_by_dropbox' => true,'sort' => 'dropbox_name'}
914
- query['workspace_id']=@workspace_info['id'] unless @workspace_info['id'].eql?(:undefined)
523
+ query = {'embed[]' => 'dropbox', 'aggregate_permissions_by_dropbox' => true, 'sort' => 'dropbox_name'}
524
+ query['workspace_id'] = current_workspace_info['id'] unless current_workspace_info['id'].eql?(:undefined)
915
525
  end
916
- return {type: :object_list,data: aoc_api.read('dropbox_memberships',query)[:data],fields: ['dropbox_id','dropbox.name']}
526
+ return {type: :object_list, data: aoc_api.read('dropbox_memberships', query)[:data], fields: ['dropbox_id', 'dropbox.name']}
917
527
  when :show
918
- return {type: :single_object,data: aoc_api.read(get_resource_path_from_args('dropboxes'),query)[:data]}
528
+ return {type: :single_object, data: aoc_api.read(get_resource_path_from_args('dropboxes'), query)[:data]}
919
529
  end
920
530
  when :send
921
- package_data = options.get_option(:value,is_type: :mandatory)
922
- raise CliBadArgument,'value must be hash, refer to doc' unless package_data.is_a?(Hash)
923
-
924
- if !@url_token_data.nil?
531
+ package_data = options.get_option(:value, is_type: :mandatory)
532
+ raise CliBadArgument, 'value must be hash, refer to doc' unless package_data.is_a?(Hash)
533
+ new_user_option = options.get_option(:new_user_option)
534
+ option_validate = options.get_option(:validate_metadata)
535
+ # works for both normal usr auth and link auth
536
+ package_data['workspace_id'] ||= current_workspace_info['id']
537
+
538
+ if !aoc_api.url_token_data.nil?
925
539
  assert_public_link_types(%w[send_package_to_user send_package_to_dropbox])
926
- box_type = @url_token_data['purpose'].split('_').last
927
- package_data['recipients'] = [{'id' => @url_token_data['data']["#{box_type}_id"],'type' => box_type}]
928
- # TODO: probably this line is not needed
929
- @workspace_info['id'] = @url_token_data['data']['workspace_id']
540
+ box_type = aoc_api.url_token_data['purpose'].split('_').last
541
+ package_data['recipients'] = [{'id' => aoc_api.url_token_data['data']["#{box_type}_id"], 'type' => box_type}]
542
+ # enforce workspace id from link (should be already ok, but in case user wanted to override)
543
+ package_data['workspace_id'] = aoc_api.url_token_data['data']['workspace_id']
930
544
  end
931
545
 
932
- package_data['workspace_id'] = @workspace_info['id']
933
-
934
- # list of files to include in package, optional
935
- #package_data['file_names']=self.transfer.ts_source_paths.map{|i|File.basename(i['source'])}
936
-
937
- # lookup users
938
- resolve_package_recipients(package_data,'recipients')
939
- resolve_package_recipients(package_data,'bcc_recipients')
940
- normalize_metadata(package_data)
941
- validate_metadata(package_data) if options.get_option(:validate_metadata)
942
-
943
- # create a new package container
944
- package_info = aoc_api.create('packages',package_data)[:data]
945
-
946
- # get node information for the node on which package must be created
947
- node_info = aoc_api.read("nodes/#{package_info['node_id']}")[:data]
948
-
949
- # tell AoC what to expect in package: 1 transfer (can also be done after transfer)
950
- # TODO: if multisession was used we should probably tell
951
- # also, currently no "multi-source" , i.e. only from client-side files, unless "node" agent is used
952
- aoc_api.update("packages/#{package_info['id']}",{'sent' => true,'transfers_expected' => 1})[:data]
953
-
954
- # get destination: package folder
955
- node_file = {node_info: node_info, file_id: package_info['contents_file_id']}
956
- # execute transfer, raise exception if at least one error
957
- Main.result_transfer(transfer_start(AoC::PACKAGES_APP,Fasp::TransferSpec::DIRECTION_SEND,node_file,AoC.package_tags(package_info,'upload')))
958
- # return all info on package
959
- return { type: :single_object, data: package_info}
546
+ # transfer may raise an error
547
+ created_package = aoc_api.create_package_simple(package_data, option_validate, new_user_option)
548
+ Main.result_transfer(transfer.start(created_package[:spec], rest_token: created_package[:node]))
549
+ # return all info on package (especially package id)
550
+ return { type: :single_object, data: created_package[:info]}
960
551
  when :recv
961
- if !@url_token_data.nil?
552
+ if !aoc_api.url_token_data.nil?
962
553
  assert_public_link_types(['view_received_package'])
963
- options.set_option(:id,@url_token_data['data']['package_id'])
554
+ options.set_option(:id, aoc_api.url_token_data['data']['package_id'])
964
555
  end
965
556
  # scalar here
966
557
  ids_to_download = instance_identifier
967
558
  skip_ids_data = []
968
559
  skip_ids_persistency = nil
969
- if options.get_option(:once_only,is_type: :mandatory)
560
+ if options.get_option(:once_only, is_type: :mandatory)
970
561
  skip_ids_persistency = PersistencyActionOnce.new(
971
562
  manager: @agents[:persistency],
972
563
  data: skip_ids_data,
973
- id: IdGenerator.from_list(['aoc_recv',options.get_option(:url,is_type: :mandatory),@workspace_info['id']].push(*@persist_ids)))
564
+ id: IdGenerator.from_list(['aoc_recv', options.get_option(:url, is_type: :mandatory),
565
+ current_workspace_info['id']].concat(aoc_api.additional_persistence_ids)))
974
566
  end
975
- if ids_to_download.eql?(VAL_ALL)
567
+ if VAL_ALL.eql?(ids_to_download)
568
+ query = option_url_query(PACKAGE_QUERY_DEFAULT)
569
+ raise 'option query must be Hash' unless query.is_a?(Hash)
570
+ if query.key?('dropbox_name')
571
+ # convenience: specify name instead of id
572
+ raise 'not both dropbox_name and dropbox_id' if query.key?('dropbox_id')
573
+ query['dropbox_id'] = aoc_api.lookup_entity_by_name('dropboxes', query['dropbox_name'])['id']
574
+ query.delete('dropbox_name')
575
+ end
576
+ query['workspace_id'] ||= current_workspace_info['id'] unless current_workspace_info['id'].eql?(:undefined)
976
577
  # get list of packages in inbox
977
- package_info = aoc_api.read('packages',{
978
- 'archived' => false,
979
- 'exclude_dropbox_packages' => true,
980
- 'has_content' => true,
981
- 'received' => true,
982
- 'workspace_id' => @workspace_info['id']})[:data]
578
+ package_info = aoc_api.read('packages', query)[:data]
983
579
  # remove from list the ones already downloaded
984
580
  ids_to_download = package_info.map{|e|e['id']}
985
581
  # array here
986
582
  ids_to_download.reject!{|id|skip_ids_data.include?(id)}
987
- end # ALL
583
+ end # VAL_ALL
988
584
  # list here
989
585
  ids_to_download = [ids_to_download] unless ids_to_download.is_a?(Array)
990
586
  result_transfer = []
991
- self.format.display_status("found #{ids_to_download.length} package(s).")
587
+ formatter.display_status("found #{ids_to_download.length} package(s).")
992
588
  ids_to_download.each do |package_id|
993
589
  package_info = aoc_api.read("packages/#{package_id}")[:data]
994
- node_info = aoc_api.read("nodes/#{package_info['node_id']}")[:data]
995
- self.format.display_status("downloading package: #{package_info['name']}")
996
- add_ts = {'paths' => [{'source' => '.'}]}
997
- node_file = {node_info: node_info, file_id: package_info['contents_file_id']}
998
- statuses = transfer_start(AoC::PACKAGES_APP,Fasp::TransferSpec::DIRECTION_RECEIVE,node_file,AoC.package_tags(package_info,'download').merge(add_ts))
999
- result_transfer.push({'package' => package_id,Main::STATUS_FIELD => statuses})
590
+ formatter.display_status("downloading package: #{package_info['name']}")
591
+ package_node_api = aoc_api.node_api_from(package_info: package_info, scope: AoC::SCOPE_NODE_USER)
592
+ statuses = transfer.start(
593
+ package_node_api.transfer_spec_gen4(
594
+ package_info['contents_file_id'],
595
+ Fasp::TransferSpec::DIRECTION_RECEIVE,
596
+ {'paths'=> [{'source' => '.'}]}),
597
+ rest_token: package_node_api)
598
+ result_transfer.push({'package' => package_id, Main::STATUS_FIELD => statuses})
1000
599
  # update skip list only if all transfer sessions completed
1001
600
  if TransferAgent.session_status(statuses).eql?(:success)
1002
601
  skip_ids_data.push(package_id)
@@ -1009,62 +608,57 @@ module Aspera
1009
608
  package_info = aoc_api.read("packages/#{package_id}")[:data]
1010
609
  return { type: :single_object, data: package_info }
1011
610
  when :list
1012
- display_fields=%w[id name bytes_transferred]
1013
- query = option_url_query({'archived' => false,'exclude_dropbox_packages' => true,'has_content' => true,'received' => true})
1014
- if query.has_key?('dropbox_name')
611
+ display_fields = %w[id name bytes_transferred]
612
+ query = option_url_query(PACKAGE_QUERY_DEFAULT)
613
+ raise 'option query must be Hash' unless query.is_a?(Hash)
614
+ if query.key?('dropbox_name')
1015
615
  # convenience: specify name instead of id
1016
- raise 'not both dropbox_name and dropbox_id' if query.has_key?('dropbox_id')
1017
- query['dropbox_id'] = aoc_api.lookup_entity_by_name('dropboxes',query['dropbox_name'])['id']
616
+ raise 'not both dropbox_name and dropbox_id' if query.key?('dropbox_id')
617
+ query['dropbox_id'] = aoc_api.lookup_entity_by_name('dropboxes', query['dropbox_name'])['id']
1018
618
  query.delete('dropbox_name')
1019
619
  end
1020
- raise 'option must be Hash' unless query.is_a?(Hash)
1021
- if @workspace_info['id'].eql?(:undefined)
620
+ if current_workspace_info['id'].eql?(:undefined)
1022
621
  display_fields.push('workspace_id')
1023
622
  else
1024
- query['workspace_id'] ||= @workspace_info['id']
623
+ query['workspace_id'] ||= current_workspace_info['id']
1025
624
  end
1026
- packages = aoc_api.read('packages',query)[:data]
1027
- return {type: :object_list,data: packages,fields: display_fields}
625
+ packages = aoc_api.read('packages', query)[:data]
626
+ return {type: :object_list, data: packages, fields: display_fields}
1028
627
  when :delete
1029
628
  list_or_one = instance_identifier
1030
- return do_bulk_operation(list_or_one,'deleted') do |id|
629
+ return do_bulk_operation(list_or_one, 'deleted') do |id|
1031
630
  raise 'expecting String identifier' unless id.is_a?(String) || id.is_a?(Integer)
1032
631
  aoc_api.delete("packages/#{id}")[:data]
1033
632
  end
1034
- when *NODE4_CMD_PATH
633
+ when *Node::NODE4_READ_ACTIONS
1035
634
  package_id = options.get_next_argument('package ID')
1036
- #path = options.get_next_argument('path', mandatory: false) || '/'
1037
635
  package_info = aoc_api.read("packages/#{package_id}")[:data]
1038
- package_node_file = {
1039
- node_info: aoc_api.read("nodes/#{package_info['node_id']}")[:data],
1040
- file_id: package_info['file_id']
1041
- }
1042
- return execute_node_gen4_command(package_command,package_node_file)
636
+ return execute_nodegen4_command(package_command, package_info['node_id'], file_id: package_info['file_id'], scope: AoC::SCOPE_NODE_USER)
1043
637
  end
1044
638
  when :files
1045
- # get workspace related information
1046
- set_workspace_info
1047
- set_home_node_file
1048
- command_repo = options.get_next_command([NODE4_COMMANDS,:short_link].flatten)
639
+ command_repo = options.get_next_command([:short_link].concat(NODE4_EXT_COMMANDS))
1049
640
  case command_repo
1050
- when *NODE4_COMMANDS then return execute_node_gen4_command(command_repo,@home_node_file)
641
+ when *NODE4_EXT_COMMANDS
642
+ return execute_nodegen4_command(command_repo, home_info[:node_id], file_id: home_info[:file_id], scope: AoC::SCOPE_NODE_USER)
1051
643
  when :short_link
644
+ # TODO: move to permissions ?
1052
645
  folder_dest = options.get_option(:to_folder)
1053
646
  value_option = options.get_option(:value)
1054
647
  case value_option
1055
648
  when 'public' then value_option = {'purpose' => 'token_auth_redirection'}
1056
649
  when 'private' then value_option = {'purpose' => 'shared_folder_auth_link'}
1057
- when NilClass,Hash then nil # keep value
650
+ when NilClass, Hash then nil # keep value
1058
651
  else raise 'value must be either: public, private, Hash or nil'
1059
652
  end
1060
653
  create_params = nil
1061
- node_file = nil
654
+ shared_apfid = nil
1062
655
  if !folder_dest.nil?
1063
- node_file = aoc_api.resolve_node_file(@home_node_file,folder_dest)
656
+ home_node_api = aoc_api.node_api_from(node_id: home_info[:node_id], workspace_info: current_workspace_info, scope: AoC::SCOPE_NODE_USER)
657
+ shared_apfid = home_node_api.resolve_api_fid(home_info[:file_id], folder_dest)
1064
658
  create_params = {
1065
- file_id: node_file[:file_id],
1066
- node_id: node_file[:node_info]['id'],
1067
- workspace_id: @workspace_info['id']
659
+ file_id: shared_apfid[:file_id],
660
+ node_id: shared_apfid[:api].app_info[:node_info]['id'],
661
+ workspace_id: current_workspace_info['id']
1068
662
  }
1069
663
  end
1070
664
  if !value_option.nil? && !create_params.nil?
@@ -1085,30 +679,29 @@ module Aspera
1085
679
  else
1086
680
  raise 'purpose must be one of: token_auth_redirection or shared_folder_auth_link'
1087
681
  end
1088
- options.set_option(:value,value_option)
682
+ options.set_option(:value, value_option)
1089
683
  end
1090
- result = entity_action(@api_aoc,'short_links',id_default: 'self')
1091
- if result[:data].is_a?(Hash) && result[:data].has_key?('created_at') && result[:data]['resource_type'].eql?('UrlToken')
1092
- node_api = aoc_api.get_node_api(node_file[:node_info])
684
+ result = entity_action(aoc_api, 'short_links', id_default: 'self')
685
+ if result[:data].is_a?(Hash) && result[:data].key?('created_at') && result[:data]['resource_type'].eql?('UrlToken')
1093
686
  # TODO: access level as arg
1094
- access_levels = Aspera::Node::ACCESS_LEVELS #['delete','list','mkdir','preview','read','rename','write']
687
+ access_levels = Aspera::Node::ACCESS_LEVELS # ['delete','list','mkdir','preview','read','rename','write']
1095
688
  perm_data = {
1096
- 'file_id' => node_file[:file_id],
689
+ 'file_id' => shared_apfid[:file_id],
1097
690
  'access_type' => 'user',
1098
691
  'access_id' => result[:data]['resource_id'],
1099
692
  'access_levels' => access_levels,
1100
693
  'tags' => {
1101
694
  'url_token' => true,
1102
- 'workspace_id' => @workspace_info['id'],
1103
- 'workspace_name' => @workspace_info['name'],
695
+ 'workspace_id' => current_workspace_info['id'],
696
+ 'workspace_name' => current_workspace_info['name'],
1104
697
  'folder_name' => 'my folder',
1105
- 'created_by_name' => aoc_api.user_info['name'],
1106
- 'created_by_email' => aoc_api.user_info['email'],
1107
- 'access_key' => node_file[:node_info]['access_key'],
1108
- 'node' => node_file[:node_info]['host']
698
+ 'created_by_name' => aoc_api.current_user_info['name'],
699
+ 'created_by_email' => aoc_api.current_user_info['email'],
700
+ 'access_key' => shared_apfid[:api].app_info[:node_info]['access_key'],
701
+ 'node' => shared_apfid[:api].app_info[:node_info]['host']
1109
702
  }
1110
703
  }
1111
- node_api.create("permissions?file_id=#{node_file[:file_id]}",perm_data)
704
+ shared_apfid[:api].create("permissions?file_id=#{shared_apfid[:file_id]}", perm_data)
1112
705
  # TODO: event ?
1113
706
  end
1114
707
  return result
@@ -1118,49 +711,58 @@ module Aspera
1118
711
  Log.log.warn('BETA: work under progress')
1119
712
  # automation api is not in the same place
1120
713
  automation_rest_params = aoc_api.params.clone
1121
- automation_rest_params[:base_url].gsub!('/api/','/automation/')
714
+ automation_rest_params[:base_url].gsub!('/api/', '/automation/')
1122
715
  automation_api = Rest.new(automation_rest_params)
1123
716
  command_automation = options.get_next_command(%i[workflows instances])
1124
717
  case command_automation
1125
718
  when :instances
1126
- return entity_action(@api_aoc,'workflow_instances')
719
+ return entity_action(aoc_api, 'workflow_instances')
1127
720
  when :workflows
1128
- wf_command = options.get_next_command([Plugin::ALL_OPS,:action,:launch].flatten)
721
+ wf_command = options.get_next_command(%i[action launch].concat(Plugin::ALL_OPS))
1129
722
  case wf_command
1130
723
  when *Plugin::ALL_OPS
1131
- return entity_command(wf_command,automation_api,'workflows',id_default: :id)
724
+ return entity_command(wf_command, automation_api, 'workflows', id_default: :id)
1132
725
  when :launch
1133
726
  wf_id = instance_identifier
1134
- data = automation_api.create("workflows/#{wf_id}/launch",{})[:data]
1135
- return {type: :single_object,data: data}
727
+ data = automation_api.create("workflows/#{wf_id}/launch", {})[:data]
728
+ return {type: :single_object, data: data}
1136
729
  when :action
1137
- #TODO: not complete
730
+ # TODO: not complete
1138
731
  wf_id = instance_identifier
1139
732
  wf_action_cmd = options.get_next_command(%i[list create show])
1140
- Log.log.warn("Not implemented: #{wf_action_cmd}")
1141
- step = automation_api.create('steps',{'workflow_id' => wf_id})[:data]
1142
- automation_api.update("workflows/#{wf_id}",{'step_order' => [step['id']]})
1143
- action = automation_api.create('actions',{'step_id' => step['id'],'type' => 'manual'})[:data]
1144
- automation_api.update("steps/#{step['id']}",{'action_order' => [action['id']]})
733
+ Log.log.warn{"Not implemented: #{wf_action_cmd}"}
734
+ step = automation_api.create('steps', {'workflow_id' => wf_id})[:data]
735
+ automation_api.update("workflows/#{wf_id}", {'step_order' => [step['id']]})
736
+ action = automation_api.create('actions', {'step_id' => step['id'], 'type' => 'manual'})[:data]
737
+ automation_api.update("steps/#{step['id']}", {'action_order' => [action['id']]})
1145
738
  wf = automation_api.read("workflows/#{wf_id}")[:data]
1146
- return {type: :single_object,data: wf}
739
+ return {type: :single_object, data: wf}
1147
740
  end
1148
741
  end
1149
742
  when :admin
1150
743
  return execute_admin_action
1151
744
  when :gateway
1152
- set_workspace_info
1153
745
  require 'aspera/faspex_gw'
1154
- FaspexGW.new(@api_aoc,@workspace_info['id']).start_server
746
+ url = options.get_option(:value, is_type: :mandatory)
747
+ uri = URI.parse(url)
748
+ server = WebServerSimple.new(uri)
749
+ server.mount(uri.path, Faspex4GWServlet, aoc_api, current_workspace_info['id'])
750
+ trap('INT') { server.shutdown }
751
+ formatter.display_status("Faspex 4 gateway listening on #{url}")
752
+ Log.log.info("Listening on #{url}")
753
+ # this is blocking until server exits
754
+ server.start
755
+ return Main.result_status('Gateway terminated')
1155
756
  else
1156
757
  raise "internal error: #{command}"
1157
758
  end # action
1158
759
  raise 'internal error: command shall return'
1159
760
  end
1160
761
 
1161
- private :aoc_params,:set_workspace_info,:set_home_node_file,:do_bulk_operation,:resolve_package_recipients,:option_url_query,:assert_public_link_types,
762
+ private :aoc_params,
763
+ :home_info,
764
+ :assert_public_link_types,
1162
765
  :execute_admin_action
1163
- private_constant :VAL_ALL,:NODE4_COMMANDS, :ID_AK_ADMIN
1164
766
  end # AoC
1165
767
  end # Plugins
1166
768
  end # Cli