aspera-cli 4.10.0 → 4.12.0

Sign up to get free protection for your applications and to get access to all the features.
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