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