aspera-cli 4.9.0 → 4.11.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 (95) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/BUGS.md +20 -0
  4. data/CHANGELOG.md +509 -0
  5. data/CONTRIBUTING.md +118 -0
  6. data/README.md +1241 -916
  7. data/bin/ascli +4 -4
  8. data/bin/asession +11 -11
  9. data/docs/test_env.conf +32 -21
  10. data/examples/aoc.rb +4 -4
  11. data/examples/dascli +16 -9
  12. data/examples/faspex4.rb +8 -8
  13. data/examples/node.rb +12 -12
  14. data/examples/server.rb +10 -10
  15. data/lib/aspera/aoc.rb +273 -266
  16. data/lib/aspera/ascmd.rb +56 -54
  17. data/lib/aspera/ats_api.rb +4 -4
  18. data/lib/aspera/cli/basic_auth_plugin.rb +15 -12
  19. data/lib/aspera/cli/extended_value.rb +5 -5
  20. data/lib/aspera/cli/formater.rb +64 -64
  21. data/lib/aspera/cli/info.rb +2 -2
  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 +14 -19
  26. data/lib/aspera/cli/main.rb +66 -67
  27. data/lib/aspera/cli/manager.rb +112 -110
  28. data/lib/aspera/cli/plugin.rb +57 -36
  29. data/lib/aspera/cli/plugins/alee.rb +4 -4
  30. data/lib/aspera/cli/plugins/aoc.rb +309 -670
  31. data/lib/aspera/cli/plugins/ats.rb +44 -46
  32. data/lib/aspera/cli/plugins/bss.rb +10 -10
  33. data/lib/aspera/cli/plugins/config.rb +497 -378
  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 +112 -114
  37. data/lib/aspera/cli/plugins/faspex5.rb +71 -46
  38. data/lib/aspera/cli/plugins/node.rb +379 -283
  39. data/lib/aspera/cli/plugins/orchestrator.rb +46 -46
  40. data/lib/aspera/cli/plugins/preview.rb +122 -114
  41. data/lib/aspera/cli/plugins/server.rb +137 -83
  42. data/lib/aspera/cli/plugins/shares.rb +30 -29
  43. data/lib/aspera/cli/plugins/sync.rb +13 -33
  44. data/lib/aspera/cli/transfer_agent.rb +60 -59
  45. data/lib/aspera/cli/version.rb +1 -1
  46. data/lib/aspera/colors.rb +3 -3
  47. data/lib/aspera/command_line_builder.rb +27 -27
  48. data/lib/aspera/cos_node.rb +22 -20
  49. data/lib/aspera/data_repository.rb +1 -1
  50. data/lib/aspera/environment.rb +35 -15
  51. data/lib/aspera/fasp/agent_base.rb +15 -15
  52. data/lib/aspera/fasp/agent_connect.rb +23 -21
  53. data/lib/aspera/fasp/agent_direct.rb +66 -64
  54. data/lib/aspera/fasp/agent_httpgw.rb +141 -78
  55. data/lib/aspera/fasp/agent_node.rb +23 -21
  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 +79 -79
  60. data/lib/aspera/fasp/listener.rb +1 -1
  61. data/lib/aspera/fasp/parameters.rb +86 -71
  62. data/lib/aspera/fasp/parameters.yaml +7 -4
  63. data/lib/aspera/fasp/resume_policy.rb +8 -8
  64. data/lib/aspera/fasp/transfer_spec.rb +35 -2
  65. data/lib/aspera/fasp/uri.rb +7 -7
  66. data/lib/aspera/faspex_gw.rb +7 -5
  67. data/lib/aspera/hash_ext.rb +3 -3
  68. data/lib/aspera/id_generator.rb +5 -5
  69. data/lib/aspera/keychain/encrypted_hash.rb +38 -105
  70. data/lib/aspera/keychain/macos_security.rb +128 -57
  71. data/lib/aspera/log.rb +7 -7
  72. data/lib/aspera/nagios.rb +19 -18
  73. data/lib/aspera/node.rb +209 -35
  74. data/lib/aspera/oauth.rb +37 -36
  75. data/lib/aspera/open_application.rb +19 -11
  76. data/lib/aspera/persistency_action_once.rb +4 -4
  77. data/lib/aspera/persistency_folder.rb +16 -15
  78. data/lib/aspera/preview/file_types.rb +8 -8
  79. data/lib/aspera/preview/generator.rb +67 -67
  80. data/lib/aspera/preview/utils.rb +27 -27
  81. data/lib/aspera/proxy_auto_config.js +41 -41
  82. data/lib/aspera/proxy_auto_config.rb +21 -14
  83. data/lib/aspera/rest.rb +72 -67
  84. data/lib/aspera/rest_call_error.rb +2 -1
  85. data/lib/aspera/rest_error_analyzer.rb +18 -17
  86. data/lib/aspera/rest_errors_aspera.rb +16 -16
  87. data/lib/aspera/secret_hider.rb +15 -13
  88. data/lib/aspera/ssh.rb +11 -10
  89. data/lib/aspera/sync.rb +158 -44
  90. data/lib/aspera/temp_file_manager.rb +2 -2
  91. data/lib/aspera/uri_reader.rb +4 -4
  92. data/lib/aspera/web_auth.rb +14 -13
  93. data.tar.gz.sig +0 -0
  94. metadata +11 -36
  95. metadata.gz.sig +0 -0
@@ -1,10 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'aspera/cli/basic_auth_plugin'
4
+ require 'aspera/cli/plugins/sync'
4
5
  require 'aspera/nagios'
5
6
  require 'aspera/hash_ext'
6
7
  require 'aspera/id_generator'
7
8
  require 'aspera/node'
9
+ require 'aspera/aoc'
8
10
  require 'aspera/fasp/transfer_spec'
9
11
  require 'base64'
10
12
  require 'zlib'
@@ -12,7 +14,7 @@ require 'zlib'
12
14
  module Aspera
13
15
  module Cli
14
16
  module Plugins
15
- class Node < BasicAuthPlugin
17
+ class Node < Aspera::Cli::BasicAuthPlugin
16
18
  class << self
17
19
  def detect(base_url)
18
20
  api = Rest.new({ base_url: base_url})
@@ -22,55 +24,99 @@ module Aspera
22
24
  end
23
25
  return nil
24
26
  end
27
+
28
+ def register_node_options(env)
29
+ env[:options].add_opt_simple(:validator, 'identifier of validator (optional for central)')
30
+ env[:options].add_opt_simple(:asperabrowserurl, 'URL for simple aspera web ui')
31
+ env[:options].add_opt_simple(:sync_name, 'sync name')
32
+ env[:options].add_opt_simple(:path, 'file or folder path for gen4 operation "file"')
33
+ env[:options].add_opt_list(:token_type, %i[aspera basic hybrid], 'Type of token used for transfers')
34
+ env[:options].add_opt_boolean(:default_ports, 'use standard FASP ports or get from node api (gen4)')
35
+ env[:options].set_option(:asperabrowserurl, 'https://asperabrowser.mybluemix.net')
36
+ env[:options].set_option(:token_type, :aspera)
37
+ env[:options].set_option(:default_ports, :yes)
38
+ env[:options].parse_options!
39
+ Aspera::Node.use_standard_ports = env[:options].get_option(:default_ports)
40
+ end
25
41
  end
26
- SAMPLE_SOAP_CALL = '<?xml version="1.0" encoding="UTF-8"?>'\
42
+
43
+ # SOAP API call to test central API
44
+ CENTRAL_SOAP_API_TEST = '<?xml version="1.0" encoding="UTF-8"?>'\
27
45
  '<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:typ="urn:Aspera:XML:FASPSessionNET:2009/11:Types">'\
28
46
  '<soapenv:Header></soapenv:Header>'\
29
47
  '<soapenv:Body><typ:GetSessionInfoRequest><SessionFilter><SessionStatus>running</SessionStatus></SessionFilter></typ:GetSessionInfoRequest></soapenv:Body>'\
30
48
  '</soapenv:Envelope>'
31
- SEARCH_REMOVE_FIELDS=%w[basename permissions].freeze
32
- private_constant :SAMPLE_SOAP_CALL,:SEARCH_REMOVE_FIELDS
49
+
50
+ # fields removed in result of search
51
+ SEARCH_REMOVE_FIELDS = %w[basename permissions].freeze
52
+
53
+ # actions in execute_command_gen3
54
+ COMMANDS_GEN3 = %i[search space mkdir mklink mkfile rename delete browse upload download]
55
+
56
+ BASE_ACTIONS = %i[api_details].concat(COMMANDS_GEN3).freeze
57
+
58
+ SPECIAL_ACTIONS = %i[health events info license].freeze
59
+
60
+ # actions available in v3 in gen4
61
+ V3_IN_V4_ACTIONS = %i[access_key].concat(BASE_ACTIONS).concat(SPECIAL_ACTIONS).freeze
62
+
63
+ # actions used commonly when a node is involved
64
+ COMMON_ACTIONS = %i[access_key].concat(BASE_ACTIONS).concat(SPECIAL_ACTIONS).freeze
65
+
66
+ private_constant(*%i[CENTRAL_SOAP_API_TEST SEARCH_REMOVE_FIELDS BASE_ACTIONS SPECIAL_ACTIONS V3_IN_V4_ACTIONS COMMON_ACTIONS])
67
+
68
+ # used in aoc
69
+ NODE4_READ_ACTIONS = %i[bearer_token_node node_info browse find].freeze
70
+
71
+ # commands for execute_command_gen4
72
+ COMMANDS_GEN4 = %i[mkdir rename delete upload download sync http_node_download file v3].concat(NODE4_READ_ACTIONS).freeze
73
+
74
+ COMMANDS_COS = %i[upload download info access_key api_details transfer].freeze
75
+ COMMANDS_SHARES = (BASE_ACTIONS - %i[search]).freeze
76
+ COMMANDS_FASPEX = COMMON_ACTIONS
33
77
 
34
78
  def initialize(env)
35
79
  super(env)
36
- # this is added to transfer spec, for instance to add tags (COS)
37
- @add_request_param = env[:add_request_param] || {}
38
- options.add_opt_simple(:validator,'identifier of validator (optional for central)')
39
- options.add_opt_simple(:asperabrowserurl,'URL for simple aspera web ui')
40
- options.add_opt_simple(:sync_name,'sync name')
41
- options.add_opt_list(:token_type,%i[aspera basic hybrid],'Type of token used for transfers')
42
- options.set_option(:asperabrowserurl,'https://asperabrowser.mybluemix.net')
43
- options.set_option(:token_type,:aspera)
44
- options.parse_options!
80
+ self.class.register_node_options(env) unless env[:skip_node_options]
45
81
  return if env[:man_only]
46
82
  @api_node =
47
- if env.has_key?(:node_api)
83
+ if env.key?(:node_api)
84
+ # this can be Aspera::Node or Aspera::Rest (shares)
48
85
  env[:node_api]
49
- elsif options.get_option(:password,is_type: :mandatory).start_with?('Bearer ')
86
+ elsif options.get_option(:password, is_type: :mandatory).start_with?('Bearer ')
50
87
  # info is provided like node_info of aoc
51
- Rest.new({
52
- base_url: options.get_option(:url,is_type: :mandatory),
88
+ Aspera::Node.new(params: {
89
+ base_url: options.get_option(:url, is_type: :mandatory),
53
90
  headers: {
54
- 'X-Aspera-AccessKey' => options.get_option(:username,is_type: :mandatory),
55
- 'Authorization' => options.get_option(:password,is_type: :mandatory)
91
+ Aspera::Node::X_ASPERA_ACCESSKEY => options.get_option(:username, is_type: :mandatory),
92
+ 'Authorization' => options.get_option(:password, is_type: :mandatory)
56
93
  }
57
94
  })
58
95
  else
59
96
  # this is normal case
60
- basic_auth_api
97
+ Aspera::Node.new(params: {
98
+ base_url: options.get_option(:url, is_type: :mandatory),
99
+ auth: {
100
+ type: :basic,
101
+ username: options.get_option(:username, is_type: :mandatory),
102
+ password: options.get_option(:password, is_type: :mandatory)
103
+ }})
61
104
  end
62
105
  end
63
106
 
64
107
  def c_textify_browse(table_data)
65
- return table_data.map {|i| i['permissions'] = i['permissions'].map { |x| x['name'] }.join(','); i }
108
+ return table_data.map do |i|
109
+ i['permissions'] = i['permissions'].map { |x| x['name'] }.join(',')
110
+ i
111
+ end
66
112
  end
67
113
 
68
114
  # key/value is defined in main in hash_table
69
- def c_textify_bool_list_result(list,name_list)
115
+ def c_textify_bool_list_result(list, name_list)
70
116
  list.each_index do |i|
71
117
  next unless name_list.include?(list[i]['key'])
72
118
  list[i]['value'].each do |item|
73
- list.push({'key' => item['name'],'value' => item['value']})
119
+ list.push({'key' => item['name'], 'value' => item['value']})
74
120
  end
75
121
  list.delete_at(i)
76
122
  # continue at same index because we delete current one
@@ -79,7 +125,7 @@ module Aspera
79
125
  end
80
126
 
81
127
  # reduce the path from a result on given named column
82
- def c_result_remove_prefix_path(result,column,path_prefix)
128
+ def c_result_remove_prefix_path(result, column, path_prefix)
83
129
  if !path_prefix.nil?
84
130
  case result[:type]
85
131
  when :object_list
@@ -95,338 +141,376 @@ module Aspera
95
141
  end
96
142
 
97
143
  # translates paths results into CLI result, and removes prefix
98
- def c_result_translate_rem_prefix(resp,type,success_msg,path_prefix)
99
- resres = { data: [], type: :object_list, fields: [type,'result']}
144
+ def c_result_translate_rem_prefix(resp, type, success_msg, path_prefix)
145
+ errors = []
146
+ resres = { data: [], type: :object_list, fields: [type, 'result']}
100
147
  JSON.parse(resp[:http].body)['paths'].each do |p|
101
148
  result = success_msg
102
- if p.has_key?('error')
103
- Log.log.error("#{p['error']['user_message']} : #{p['path']}")
149
+ if p.key?('error')
150
+ Log.log.error{"#{p['error']['user_message']} : #{p['path']}"}
104
151
  result = 'ERROR: ' + p['error']['user_message']
152
+ errors.push([p['path'], p['error']['user_message']])
105
153
  end
106
- resres[:data].push({type => p['path'],'result' => result})
154
+ resres[:data].push({type => p['path'], 'result' => result})
155
+ end
156
+ # one error make all fail
157
+ unless errors.empty?
158
+ raise errors.map{|i|"#{i.first}: #{i.last}"}.join(', ')
107
159
  end
108
- return c_result_remove_prefix_path(resres,type,path_prefix)
160
+ return c_result_remove_prefix_path(resres, type, path_prefix)
109
161
  end
110
162
 
111
163
  # get path arguments from command line, and add prefix
112
- def get_next_arg_add_prefix(path_prefix,name,number=:single)
113
- thepath = options.get_next_argument(name,expected: number)
164
+ def get_next_arg_add_prefix(path_prefix, name, number=:single)
165
+ thepath = options.get_next_argument(name, expected: number)
114
166
  return thepath if path_prefix.nil?
115
- return File.join(path_prefix,thepath) if thepath.is_a?(String)
116
- return thepath.map {|p| File.join(path_prefix,p)} if thepath.is_a?(Array)
117
- raise StandardError,'expect: nil, String or Array'
167
+ return File.join(path_prefix, thepath) if thepath.is_a?(String)
168
+ return thepath.map {|p| File.join(path_prefix, p)} if thepath.is_a?(Array)
169
+ raise StandardError, 'expect: nil, String or Array'
118
170
  end
119
171
 
120
- SIMPLE_ACTIONS = %i[health events space info license mkdir mklink mkfile rename delete search].freeze
121
-
122
- COMMON_ACTIONS = %i[browse upload download api_details].concat(SIMPLE_ACTIONS).freeze
123
-
124
- # common API to node and Shares
125
- # prefix_path is used to list remote sources in Faspex
126
- def execute_simple_common(command,prefix_path)
172
+ # file and folder related commands
173
+ def execute_command_gen3(command, prefix_path)
127
174
  case command
128
- when :health
129
- nagios = Nagios.new
130
- begin
131
- info = @api_node.read('info')[:data]
132
- nagios.add_ok('node api','accessible')
133
- nagios.check_time_offset(info['current_time'],'node api')
134
- nagios.check_product_version('node api','entsrv', info['version'])
135
- rescue StandardError => e
136
- nagios.add_critical('node api',e.to_s)
137
- end
138
- begin
139
- @api_node.call(
140
- operation: 'POST',
141
- subpath: 'services/soap/Transfer-201210',
142
- headers: {'Content-Type' => 'text/xml;charset=UTF-8','SOAPAction' => 'FASPSessionNET-200911#GetSessionInfo'},
143
- text_body_params: SAMPLE_SOAP_CALL)[:http].body
144
- nagios.add_ok('central','accessible by node')
145
- rescue StandardError => e
146
- nagios.add_critical('central',e.to_s)
147
- end
148
- return nagios.result
149
- when :events
150
- events = @api_node.read('events',options.get_option(:value))[:data]
151
- return { type: :object_list, data: events}
152
- when :info
153
- node_info = @api_node.read('info')[:data]
154
- return { type: :single_object, data: node_info, textify: lambda { |table_data| c_textify_bool_list_result(table_data,%w[capabilities settings])}}
155
- when :license # requires: asnodeadmin -mu <node user> --acl-add=internal --internal
156
- node_license = @api_node.read('license')[:data]
157
- if node_license['failure'].is_a?(String) && node_license['failure'].include?('ACL')
158
- Log.log.error('server must have: asnodeadmin -mu <node user> --acl-add=internal --internal')
159
- end
160
- return { type: :single_object, data: node_license}
161
175
  when :delete
162
- paths_to_delete = get_next_arg_add_prefix(prefix_path,'file list',:multiple)
163
- resp = @api_node.create('files/delete',{ paths: paths_to_delete.map{|i| {'path' => i.start_with?('/') ? i : '/' + i} }})
164
- return c_result_translate_rem_prefix(resp,'file','deleted',prefix_path)
176
+ paths_to_delete = get_next_arg_add_prefix(prefix_path, 'file list', :multiple)
177
+ resp = @api_node.create('files/delete', { paths: paths_to_delete.map{|i| {'path' => i.start_with?('/') ? i : '/' + i} }})
178
+ return c_result_translate_rem_prefix(resp, 'file', 'deleted', prefix_path)
165
179
  when :search
166
- search_root = get_next_arg_add_prefix(prefix_path,'search root')
180
+ search_root = get_next_arg_add_prefix(prefix_path, 'search root')
167
181
  parameters = {'path' => search_root}
168
182
  other_options = options.get_option(:value)
169
183
  parameters.merge!(other_options) unless other_options.nil?
170
- resp = @api_node.create('files/search',parameters)
184
+ resp = @api_node.create('files/search', parameters)
171
185
  result = { type: :object_list, data: resp[:data]['items']}
172
186
  return Main.result_empty if result[:data].empty?
173
187
  result[:fields] = result[:data].first.keys.reject{|i|SEARCH_REMOVE_FIELDS.include?(i)}
174
188
  self.format.display_status("Items: #{resp[:data]['item_count']}/#{resp[:data]['total_count']}")
175
189
  self.format.display_status("params: #{resp[:data]['parameters'].keys.map{|k|"#{k}:#{resp[:data]['parameters'][k]}"}.join(',')}")
176
- return c_result_remove_prefix_path(result,'path',prefix_path)
190
+ return c_result_remove_prefix_path(result, 'path', prefix_path)
177
191
  when :space
178
- # TODO: could be a list of path
179
- path_list = get_next_arg_add_prefix(prefix_path,'folder path or ext.val. list')
192
+ path_list = get_next_arg_add_prefix(prefix_path, 'folder path or ext.val. list')
180
193
  path_list = [path_list] unless path_list.is_a?(Array)
181
- resp = @api_node.create('space',{ 'paths' => path_list.map {|i| { path: i} } })
194
+ resp = @api_node.create('space', { 'paths' => path_list.map {|i| { path: i} } })
182
195
  result = { data: resp[:data]['paths'], type: :object_list}
183
- #return c_result_translate_rem_prefix(resp,'folder','created',prefix_path)
184
- return c_result_remove_prefix_path(result,'path',prefix_path)
196
+ # return c_result_translate_rem_prefix(resp,'folder','created',prefix_path)
197
+ return c_result_remove_prefix_path(result, 'path', prefix_path)
185
198
  when :mkdir
186
- path_list = get_next_arg_add_prefix(prefix_path,'folder path or ext.val. list')
199
+ path_list = get_next_arg_add_prefix(prefix_path, 'folder path or ext.val. list')
187
200
  path_list = [path_list] unless path_list.is_a?(Array)
188
- #TODO: a command for that ?
189
- #resp=@api_node.create('space',{ "paths" => path_list.map {|i| { type: :directory, path: i} } } )
190
- resp = @api_node.create('files/create',{ 'paths' => [{ type: :directory, path: path_list }] })
191
- return c_result_translate_rem_prefix(resp,'folder','created',prefix_path)
201
+ resp = @api_node.create('files/create', { 'paths' => [{ type: :directory, path: path_list }] })
202
+ return c_result_translate_rem_prefix(resp, 'folder', 'created', prefix_path)
192
203
  when :mklink
193
- target = get_next_arg_add_prefix(prefix_path,'target')
194
- path_list = get_next_arg_add_prefix(prefix_path,'link path')
195
- resp = @api_node.create('files/create',{ 'paths' => [{ type: :symbolic_link, path: path_list, target: { path: target} }] })
196
- return c_result_translate_rem_prefix(resp,'folder','created',prefix_path)
204
+ target = get_next_arg_add_prefix(prefix_path, 'target')
205
+ path_list = get_next_arg_add_prefix(prefix_path, 'link path')
206
+ resp = @api_node.create('files/create', { 'paths' => [{ type: :symbolic_link, path: path_list, target: { path: target} }] })
207
+ return c_result_translate_rem_prefix(resp, 'folder', 'created', prefix_path)
197
208
  when :mkfile
198
- path_list = get_next_arg_add_prefix(prefix_path,'file path')
209
+ path_list = get_next_arg_add_prefix(prefix_path, 'file path')
199
210
  contents64 = Base64.strict_encode64(options.get_next_argument('contents'))
200
- resp = @api_node.create('files/create',{ 'paths' => [{ type: :file, path: path_list, contents: contents64 }] })
201
- return c_result_translate_rem_prefix(resp,'folder','created',prefix_path)
211
+ resp = @api_node.create('files/create', { 'paths' => [{ type: :file, path: path_list, contents: contents64 }] })
212
+ return c_result_translate_rem_prefix(resp, 'folder', 'created', prefix_path)
202
213
  when :rename
203
- path_base = get_next_arg_add_prefix(prefix_path,'path_base')
204
- path_src = get_next_arg_add_prefix(prefix_path,'path_src')
205
- path_dst = get_next_arg_add_prefix(prefix_path,'path_dst')
206
- resp = @api_node.create('files/rename',{ 'paths' => [{ 'path' => path_base, 'source' => path_src, 'destination' => path_dst }] })
207
- return c_result_translate_rem_prefix(resp,'entry','moved',prefix_path)
214
+ path_base = get_next_arg_add_prefix(prefix_path, 'path_base')
215
+ path_src = get_next_arg_add_prefix(prefix_path, 'path_src')
216
+ path_dst = get_next_arg_add_prefix(prefix_path, 'path_dst')
217
+ resp = @api_node.create('files/rename', { 'paths' => [{ 'path' => path_base, 'source' => path_src, 'destination' => path_dst }] })
218
+ return c_result_translate_rem_prefix(resp, 'entry', 'moved', prefix_path)
208
219
  when :browse
209
- thepath = get_next_arg_add_prefix(prefix_path,'path')
220
+ thepath = get_next_arg_add_prefix(prefix_path, 'path')
210
221
  query = { path: thepath}
211
222
  additional_query = options.get_option(:query)
212
223
  query.merge!(additional_query) unless additional_query.nil?
213
224
  send_result = @api_node.create('files/browse', query)[:data]
214
- #example: send_result={'items'=>[{'file'=>"filename1","permissions"=>[{'name'=>'read'},{'name'=>'write'}]}]}
225
+ # example: send_result={'items'=>[{'file'=>"filename1","permissions"=>[{'name'=>'read'},{'name'=>'write'}]}]}
215
226
  # if there is no items
216
227
  case send_result['self']['type']
217
- when 'directory','container' # directory: node, container: shares
228
+ when 'directory', 'container' # directory: node, container: shares
218
229
  result = { data: send_result['items'], type: :object_list, textify: lambda { |table_data| c_textify_browse(table_data) } }
219
230
  self.format.display_status("Items: #{send_result['item_count']}/#{send_result['total_count']}")
220
231
  else # 'file','symbolic_link'
221
232
  result = { data: send_result['self'], type: :single_object}
222
- #result={ data: [send_result['self']] , type: :object_list, textify: lambda { |table_data| c_textify_browse(table_data) } }
223
- #raise "unknown type: #{send_result['self']['type']}"
233
+ # result={ data: [send_result['self']] , type: :object_list, textify: lambda { |table_data| c_textify_browse(table_data) } }
234
+ # raise "unknown type: #{send_result['self']['type']}"
224
235
  end
225
- return c_result_remove_prefix_path(result,'path',prefix_path)
226
- when :upload,:download
236
+ return c_result_remove_prefix_path(result, 'path', prefix_path)
237
+ when :upload, :download
227
238
  token_type = options.get_option(:token_type)
228
239
  # nil if Shares 1.x
229
240
  token_type = :aspera if token_type.nil?
230
241
  case token_type
231
- when :aspera,:hybrid
242
+ when :aspera, :hybrid
232
243
  # empty transfer spec for authorization request
233
- request_transfer_spec={}
244
+ request_transfer_spec = {}
234
245
  # set requested paths depending on direction
235
246
  request_transfer_spec[:paths] = command.eql?(:download) ? transfer.ts_source_paths : [{ destination: transfer.destination_folder('send') }]
236
247
  # add fixed parameters if any (for COS)
237
- request_transfer_spec.deep_merge!(@add_request_param)
248
+ @api_node.add_tspec_info(request_transfer_spec) if @api_node.respond_to?(:add_tspec_info)
238
249
  # prepare payload for single request
239
- setup_payload={transfer_requests: [{transfer_request: request_transfer_spec}]}
250
+ setup_payload = {transfer_requests: [{transfer_request: request_transfer_spec}]}
240
251
  # only one request, so only one answer
241
- transfer_spec = @api_node.create("files/#{command}_setup",setup_payload)[:data]['transfer_specs'].first['transfer_spec']
252
+ transfer_spec = @api_node.create("files/#{command}_setup", setup_payload)[:data]['transfer_specs'].first['transfer_spec']
242
253
  # delete this part, as the returned value contains only destination, and not sources
243
254
  transfer_spec.delete('paths') if command.eql?(:upload)
244
255
  when :basic
245
256
  raise 'shall have auth' unless @api_node.params[:auth].is_a?(Hash)
246
257
  raise 'shall be basic auth' unless @api_node.params[:auth][:type].eql?(:basic)
247
- ts_direction =
248
- case command
249
- when :upload then Fasp::TransferSpec::DIRECTION_SEND
250
- when :download then Fasp::TransferSpec::DIRECTION_RECEIVE
251
- else raise 'Error: need upload or download'
252
- end
253
- transfer_spec = {
254
- 'remote_host' => URI.parse(@api_node.params[:base_url]).host,
255
- 'remote_user' => Aspera::Fasp::TransferSpec::ACCESS_KEY_TRANSFER_USER,
256
- 'ssh_port' => Aspera::Fasp::TransferSpec::SSH_PORT,
257
- 'direction' => ts_direction,
258
- 'destination_root' => transfer.destination_folder(ts_direction)
259
- }.deep_merge(@add_request_param)
258
+ transfer_spec = {}.merge(Aspera::Fasp::TransferSpec::AK_TSPEC_BASE)
259
+ transfer_spec['remote_host'] = URI.parse(@api_node.params[:base_url]).host
260
+ Fasp::TransferSpec.action_to_direction(transfer_spec, command)
261
+ transfer_spec['destination_root'] = transfer.destination_folder(transfer_spec['direction'])
262
+ @api_node.add_tspec_info(transfer_spec) if @api_node.respond_to?(:add_tspec_info)
260
263
  else raise "ERROR: token_type #{tt}"
261
264
  end
262
265
  if %i[basic hybrid].include?(token_type)
263
- Aspera::Node.set_ak_basic_token(transfer_spec,@api_node.params[:auth][:username],@api_node.params[:auth][:password])
266
+ @api_node.ts_basic_token(transfer_spec)
264
267
  end
265
- return Main.result_transfer(transfer.start(transfer_spec,{ src: :node_gen3}))
268
+ return Main.result_transfer(transfer.start(transfer_spec))
269
+ end
270
+ raise 'INTERNAL ERROR'
271
+ end
272
+
273
+ # common API to node and Shares
274
+ # prefix_path is used to list remote sources in Faspex
275
+ def execute_simple_common(command, prefix_path)
276
+ case command
277
+ when *COMMANDS_GEN3
278
+ execute_command_gen3(command, prefix_path)
279
+ when :access_key
280
+ ak_command = options.get_next_command([:do].concat(Plugin::ALL_OPS))
281
+ case ak_command
282
+ when *Plugin::ALL_OPS then return entity_command(ak_command, @api_node, 'access_keys', id_default: 'self')
283
+ when :do
284
+ access_key = options.get_next_argument('access key id')
285
+ ak_info = @api_node.read("access_keys/#{access_key}")[:data]
286
+ # change API credentials if different access key
287
+ if !access_key.eql?('self')
288
+ @api_node.params[:auth][:username] = ak_info['id']
289
+ @api_node.params[:auth][:password] = config.lookup_secret(url: @api_node.params[:base_url], username: ak_info['id'], mandatory: true)
290
+ end
291
+ command_repo = options.get_next_command(COMMANDS_GEN4)
292
+ return execute_command_gen4(command_repo, ak_info['root_file_id'])
293
+ end
294
+ when :health
295
+ nagios = Nagios.new
296
+ begin
297
+ info = @api_node.read('info')[:data]
298
+ nagios.add_ok('node api', 'accessible')
299
+ nagios.check_time_offset(info['current_time'], 'node api')
300
+ nagios.check_product_version('node api', 'entsrv', info['version'])
301
+ rescue StandardError => e
302
+ nagios.add_critical('node api', e.to_s)
303
+ end
304
+ begin
305
+ @api_node.call(
306
+ operation: 'POST',
307
+ subpath: 'services/soap/Transfer-201210',
308
+ headers: {'Content-Type' => 'text/xml;charset=UTF-8', 'SOAPAction' => 'FASPSessionNET-200911#GetSessionInfo'},
309
+ text_body_params: CENTRAL_SOAP_API_TEST)[:http].body
310
+ nagios.add_ok('central', 'accessible by node')
311
+ rescue StandardError => e
312
+ nagios.add_critical('central', e.to_s)
313
+ end
314
+ return nagios.result
315
+ when :events
316
+ events = @api_node.read('events', options.get_option(:value))[:data]
317
+ return { type: :object_list, data: events}
318
+ when :info
319
+ nd_info = @api_node.read('info')[:data]
320
+ return { type: :single_object, data: nd_info, textify: lambda { |table_data| c_textify_bool_list_result(table_data, %w[capabilities settings])}}
321
+ when :license
322
+ # requires: asnodeadmin -mu <node user> --acl-add=internal --internal
323
+ node_license = @api_node.read('license')[:data]
324
+ if node_license['failure'].is_a?(String) && node_license['failure'].include?('ACL')
325
+ Log.log.error('server must have: asnodeadmin -mu <node user> --acl-add=internal --internal')
326
+ end
327
+ return { type: :single_object, data: node_license}
266
328
  when :api_details
267
329
  return { type: :single_object, data: @api_node.params }
268
330
  end
269
331
  end
270
332
 
271
- # navigate the path from given file id
272
- # @param id initial file id
273
- # @param path file path
274
- # @return {.api,.file_id}
275
- def resolve_api_fid(id, path)
276
- # TODO: implement
277
- Log.log.debug("TODO #{path}")
278
- return {api: @api_node, file_id: id}
333
+ def execute_node_gen4_file_command(command_node_file, top_file_id)
334
+ file_path = options.get_option(:path)
335
+ apifid =
336
+ if file_path.nil?
337
+ {api: @api_node, file_id: instance_identifier}
338
+ else
339
+ @api_node.resolve_api_fid(top_file_id, file_path) # TODO: allow follow link ?
340
+ end
341
+ case command_node_file
342
+ when :show
343
+ items = apifid[:api].read("files/#{apifid[:file_id]}")[:data]
344
+ return {type: :single_object, data: items}
345
+ when :modify
346
+ update_param = options.get_next_argument('update data', type: Hash)
347
+ apifid[:api].update("files/#{apifid[:file_id]}", update_param)[:data]
348
+ return Main.result_status('Done')
349
+ when :permission
350
+ command_perm = options.get_next_command(%i[list create delete])
351
+ case command_perm
352
+ when :list
353
+ # generic options : TODO: as arg ? option_url_query
354
+ list_options ||= {'include' => ['[]', 'access_level', 'permission_count']}
355
+ # add which one to get
356
+ list_options['file_id'] = apifid[:file_id]
357
+ list_options['inherited'] ||= false
358
+ items = apifid[:api].read('permissions', list_options)[:data]
359
+ return {type: :object_list, data: items}
360
+ when :delete
361
+ perm_id = instance_identifier
362
+ return do_bulk_operation(perm_id, 'deleted') do |one_id|
363
+ # TODO: notify event ?
364
+ apifid[:api].delete("permissions/#{perm_id}")
365
+ {'id' => one_id}
366
+ end
367
+ when :create
368
+ create_param = options.get_next_argument('creation data', type: Hash)
369
+ raise 'no file_id' if create_param.key?('file_id')
370
+ create_param['file_id'] = apifid[:file_id]
371
+ create_param['access_levels'] = Aspera::Node::ACCESS_LEVELS unless create_param.key?('access_levels')
372
+ # add application specific tags (AoC)
373
+ the_app = apifid[:api].app_info
374
+ the_app[:api].permissions_create_params(create_param: create_param, app_info: the_app) unless the_app.nil?
375
+ # create permission
376
+ created_data = apifid[:api].create('permissions', create_param)[:data]
377
+ # bnotify application of creation
378
+ the_app[:api].permissions_create_event(created_data: created_data, app_info: the_app) unless the_app.nil?
379
+ return { type: :single_object, data: created_data}
380
+ else raise "internal error:shall not reach here (#{command_perm})"
381
+ end
382
+ else raise "internal error:shall not reach here (#{command_node_file})"
383
+ end
279
384
  end
280
385
 
281
- NODE4_COMMANDS = %i[browse find mkdir rename delete upload download http_node_download file permission bearer_token_node node_info].freeze
282
-
283
- def execute_node_gen4_command(command_repo, top_file_id)
284
- #@api_node
386
+ def execute_command_gen4(command_repo, top_file_id)
285
387
  case command_repo
286
- when :node_info
388
+ when :v3
389
+ # NOTE: other common actions are unauthorized with user scope
390
+ command_legacy = options.get_next_command(V3_IN_V4_ACTIONS)
391
+ # TODO: shall we support all methods here ? what if there is a link ?
392
+ apifid = @api_node.resolve_api_fid(top_file_id, '')
393
+ return Node.new(@agents.merge(skip_basic_auth_options: true, skip_node_options: true, node_api: apifid[:api])).execute_action(command_legacy)
394
+ when :node_info, :bearer_token_node
287
395
  thepath = options.get_next_argument('path')
288
- apifid = resolve_api_fid(top_file_id,thepath)
289
- apifid[:api] = aoc_api.get_node_api(apifid[:node_info], use_secret: false)
290
- return {type: :single_object,data: {
291
- url: apifid[:node_info]['url'],
292
- username: apifid[:node_info]['access_key'],
293
- password: apifid[:api].oauth_token,
294
- root_id: apifid[:file_id]
295
- }}
396
+ apifid = @api_node.resolve_api_fid(top_file_id, thepath)
397
+ result = {
398
+ url: apifid[:api].params[:base_url],
399
+ root_id: apifid[:file_id]
400
+ }
401
+ raise 'No auth for node' if apifid[:api].params[:auth].nil?
402
+ case apifid[:api].params[:auth][:type]
403
+ when :basic
404
+ result[:username] = apifid[:api].params[:auth][:username]
405
+ result[:password] = apifid[:api].params[:auth][:password]
406
+ when :oauth2
407
+ result[:username] = apifid[:api].params[:headers][Aspera::Node::X_ASPERA_ACCESSKEY]
408
+ result[:password] = apifid[:api].oauth_token
409
+ else raise 'unknown'
410
+ end
411
+ return {type: :single_object, data: result} if command_repo.eql?(:node_info)
412
+ raise 'not bearer token' unless result[:password].start_with?('Bearer ')
413
+ return Main.result_status(result[:password])
296
414
  when :browse
297
415
  thepath = options.get_next_argument('path')
298
- apifid = resolve_api_fid(top_file_id,thepath)
416
+ apifid = @api_node.resolve_api_fid(top_file_id, thepath)
299
417
  file_info = apifid[:api].read("files/#{apifid[:file_id]}")[:data]
300
418
  if file_info['type'].eql?('folder')
301
- result = apifid[:api].read("files/#{apifid[:file_id]}/files",options.get_option(:value))
419
+ result = apifid[:api].read("files/#{apifid[:file_id]}/files", options.get_option(:value))
302
420
  items = result[:data]
303
421
  self.format.display_status("Items: #{result[:data].length}/#{result[:http]['X-Total-Count']}")
304
422
  else
305
423
  items = [file_info]
306
424
  end
307
- return {type: :object_list,data: items,fields: %w[name type recursive_size size modified_time access_level]}
425
+ return {type: :object_list, data: items, fields: %w[name type recursive_size size modified_time access_level]}
308
426
  when :find
309
427
  thepath = options.get_next_argument('path')
310
- apifid = resolve_api_fid(top_file_id,thepath)
428
+ apifid = @api_node.resolve_api_fid(top_file_id, thepath)
311
429
  test_block = Aspera::Node.file_matcher(options.get_option(:value))
312
- return {type: :object_list,data: aoc_api.find_files(apifid,test_block),fields: ['path']}
430
+ return {type: :object_list, data: @api_node.find_files(apifid[:file_id], test_block), fields: ['path']}
313
431
  when :mkdir
314
432
  thepath = options.get_next_argument('path')
315
- containing_folder_path = thepath.split(AoC::PATH_SEPARATOR)
433
+ containing_folder_path = thepath.split(Aspera::Node::PATH_SEPARATOR)
316
434
  new_folder = containing_folder_path.pop
317
- apifid = resolve_api_fid(top_file_id,containing_folder_path.join(AoC::PATH_SEPARATOR))
318
- result = apifid[:api].create("files/#{apifid[:file_id]}/files",{name: new_folder,type: :folder})[:data]
435
+ apifid = @api_node.resolve_api_fid(top_file_id, containing_folder_path.join(Aspera::Node::PATH_SEPARATOR))
436
+ result = apifid[:api].create("files/#{apifid[:file_id]}/files", {name: new_folder, type: :folder})[:data]
319
437
  return Main.result_status("created: #{result['name']} (id=#{result['id']})")
320
438
  when :rename
321
439
  thepath = options.get_next_argument('source path')
322
440
  newname = options.get_next_argument('new name')
323
- apifid = resolve_api_fid(top_file_id,thepath)
324
- result = apifid[:api].update("files/#{apifid[:file_id]}",{name: newname})[:data]
441
+ apifid = @api_node.resolve_api_fid(top_file_id, thepath)
442
+ result = apifid[:api].update("files/#{apifid[:file_id]}", {name: newname})[:data]
325
443
  return Main.result_status("renamed #{thepath} to #{newname}")
326
444
  when :delete
327
445
  thepath = options.get_next_argument('path')
328
- return do_bulk_operation(thepath,'deleted','path') do |l_path|
446
+ return do_bulk_operation(thepath, 'deleted', id_result: 'path') do |l_path|
329
447
  raise "expecting String (path), got #{l_path.class.name} (#{l_path})" unless l_path.is_a?(String)
330
- apifid = resolve_api_fid(top_file_id,l_path)
448
+ apifid = @api_node.resolve_api_fid(top_file_id, l_path)
331
449
  result = apifid[:api].delete("files/#{apifid[:file_id]}")[:data]
332
450
  {'path' => l_path}
333
451
  end
452
+ when :sync
453
+ # remote is specified by option to_folder
454
+ apifid = @api_node.resolve_api_fid(top_file_id, transfer.destination_folder(Fasp::TransferSpec::DIRECTION_SEND))
455
+ transfer_spec = apifid[:api].transfer_spec_gen4(apifid[:file_id], Fasp::TransferSpec::DIRECTION_SEND)
456
+ Log.dump(:ts, transfer_spec)
457
+ sync_plugin = Plugins::Sync.new(@agents, transfer_spec: transfer_spec)
458
+ return sync_plugin.execute_action
334
459
  when :upload
335
- apifid = resolve_api_fid(top_file_id,transfer.destination_folder(Fasp::TransferSpec::DIRECTION_SEND))
336
- add_ts = {'tags' => {'aspera' => {'files' => {'parentCwd' => "#{apifid[:node_info]['id']}:#{apifid[:file_id]}"}}}}
337
- return Main.result_transfer(transfer_start(AoC::FILES_APP,Fasp::TransferSpec::DIRECTION_SEND,apifid,add_ts))
460
+ apifid = @api_node.resolve_api_fid(top_file_id, transfer.destination_folder(Fasp::TransferSpec::DIRECTION_SEND))
461
+ return Main.result_transfer(transfer.start(apifid[:api].transfer_spec_gen4(apifid[:file_id], Fasp::TransferSpec::DIRECTION_SEND)))
338
462
  when :download
339
463
  source_paths = transfer.ts_source_paths
340
464
  # special case for AoC : all files must be in same folder
341
465
  source_folder = source_paths.shift['source']
342
466
  # if a single file: split into folder and path
467
+ apifid = @api_node.resolve_api_fid(top_file_id, source_folder)
343
468
  if source_paths.empty?
344
- source_folder = source_folder.split(AoC::PATH_SEPARATOR)
345
- source_paths = [{'source' => source_folder.pop}]
346
- source_folder = source_folder.join(AoC::PATH_SEPARATOR)
469
+ file_info = apifid[:api].read("files/#{apifid[:file_id]}")[:data]
470
+ case file_info['type']
471
+ when 'file'
472
+ # if the single source is a file, we need to split into folder path and filename
473
+ src_dir_elements = source_folder.split(Aspera::Node::PATH_SEPARATOR)
474
+ # filename is the last one
475
+ source_paths = [{'source' => src_dir_elements.pop}]
476
+ # source folder is what remains
477
+ source_folder = src_dir_elements.join(Aspera::Node::PATH_SEPARATOR)
478
+ # TODO: instead of creating a new object, use the same, and change file id with parent folder id ? possible ?
479
+ apifid = @api_node.resolve_api_fid(top_file_id, source_folder)
480
+ when 'link', 'folder'
481
+ # single source is 'folder' or 'link'
482
+ # TODO: add this ? , 'destination'=>file_info['name']
483
+ source_paths = [{'source' => '.'}]
484
+ else
485
+ raise "Unknown source type: #{file_info['type']}"
486
+ end
347
487
  end
348
- apifid = resolve_api_fid(top_file_id,source_folder)
349
- # override paths with just filename
350
- add_ts = {'tags' => {'aspera' => {'files' => {'parentCwd' => "#{apifid[:node_info]['id']}:#{apifid[:file_id]}"}}}}
351
- add_ts['paths'] = source_paths
352
- return Main.result_transfer(transfer_start(AoC::FILES_APP,Fasp::TransferSpec::DIRECTION_RECEIVE,apifid,add_ts))
488
+ return Main.result_transfer(transfer.start(apifid[:api].transfer_spec_gen4(apifid[:file_id], Fasp::TransferSpec::DIRECTION_RECEIVE, {'paths'=>source_paths})))
353
489
  when :http_node_download
354
490
  source_paths = transfer.ts_source_paths
355
491
  source_folder = source_paths.shift['source']
356
492
  if source_paths.empty?
357
- source_folder = source_folder.split(AoC::PATH_SEPARATOR)
493
+ source_folder = source_folder.split(Aspera::Node::PATH_SEPARATOR)
358
494
  source_paths = [{'source' => source_folder.pop}]
359
- source_folder = source_folder.join(AoC::PATH_SEPARATOR)
495
+ source_folder = source_folder.join(Aspera::Node::PATH_SEPARATOR)
360
496
  end
361
- raise CliBadArgument,'one file at a time only in HTTP mode' if source_paths.length > 1
497
+ raise CliBadArgument, 'one file at a time only in HTTP mode' if source_paths.length > 1
362
498
  file_name = source_paths.first['source']
363
- apifid = resolve_api_fid(top_file_id,File.join(source_folder,file_name))
499
+ apifid = @api_node.resolve_api_fid(top_file_id, File.join(source_folder, file_name))
364
500
  apifid[:api].call(
365
501
  operation: 'GET',
366
502
  subpath: "files/#{apifid[:file_id]}/content",
367
- save_to_file: File.join(transfer.destination_folder(Fasp::TransferSpec::DIRECTION_RECEIVE),file_name))
503
+ save_to_file: File.join(transfer.destination_folder(Fasp::TransferSpec::DIRECTION_RECEIVE), file_name))
368
504
  return Main.result_status("downloaded: #{file_name}")
369
- when :permission
370
- command_perm = options.get_next_command(%i[list create])
371
- case command_perm
372
- when :list
373
- # generic options : TODO: as arg ? option_url_query
374
- list_options ||= {'include' => ['[]','access_level','permission_count']}
375
- # special value: ALL will show all permissions
376
- if !VAL_ALL.eql?(apifid[:file_id])
377
- # add which one to get
378
- list_options['file_id'] = apifid[:file_id]
379
- list_options['inherited'] ||= false
380
- end
381
- items = apifid[:api].read('permissions',list_options)[:data]
382
- return {type: :object_list,data: items}
383
- when :create
384
- #create_param=self.options.get_next_argument('creation data (Hash)')
385
- set_workspace_info
386
- access_id = "#{ID_AK_ADMIN}_WS_#{@workspace_id}"
387
- apifid[:node_info]
388
- params = {
389
- 'file_id' => apifid[:file_id], # mandatory
390
- 'access_type' => 'user', # mandatory: user or group
391
- 'access_id' => access_id, # id of user or group
392
- 'access_levels' => Aspera::Node::ACCESS_LEVELS,
393
- 'tags' => {'aspera' => {'files' => {'workspace' => {
394
- 'id' => @workspace_id,
395
- 'workspace_name' => @workspace_name,
396
- 'user_name' => aoc_api.user_info['name'],
397
- 'shared_by_user_id' => aoc_api.user_info['id'],
398
- 'shared_by_name' => aoc_api.user_info['name'],
399
- 'shared_by_email' => aoc_api.user_info['email'],
400
- 'shared_with_name' => access_id,
401
- 'access_key' => apifid[:node_info]['access_key'],
402
- 'node' => apifid[:node_info]['name']}}}}}
403
- item = apifid[:api].create('permissions',params)[:data]
404
- return {type: :single_object,data: item}
405
- else raise "internal error:shall not reach here (#{command_perm})"
406
- end
407
505
  when :file
408
- command_node_file = options.get_next_command(%i[show modify])
409
- file_path = options.get_option(:path)
410
- apifid =
411
- if !file_path.nil?
412
- resolve_api_fid(top_file_id,file_path) # TODO: allow follow link ?
413
- else
414
- {node_info: top_file_id[:node_info],file_id: instance_identifier}
415
- end
416
- case command_node_file
417
- when :show
418
- items = apifid[:api].read("files/#{apifid[:file_id]}")[:data]
419
- return {type: :single_object,data: items}
420
- when :modify
421
- update_param = options.get_next_argument('update data (Hash)')
422
- res = apifid[:api].update("files/#{apifid[:file_id]}",update_param)[:data]
423
- return {type: :single_object,data: res}
424
- else raise "internal error:shall not reach here (#{command_node_file})"
425
- end
506
+ command_node_file = options.get_next_command(%i[show modify permission])
507
+ return execute_node_gen4_file_command(command_node_file, top_file_id)
508
+ else raise "INTERNAL ERROR: no case for #{command_repo}"
426
509
  end # command_repo
427
- raise 'ERR'
428
- end # execute_node_gen4_command
510
+ # raise 'INTERNAL ERROR: missing return'
511
+ end # execute_command_gen4
429
512
 
513
+ # This is older API
430
514
  def execute_async
431
515
  command = options.get_next_command(%i[list delete files show counters bandwidth])
432
516
  unless command.eql?(:list)
@@ -441,7 +525,7 @@ module Aspera
441
525
  end
442
526
  else
443
527
  asyncids = @api_node.read('async/list')[:data]['sync_ids']
444
- summaries = @api_node.create('async/summary',{'syncs' => asyncids})[:data]['sync_summaries']
528
+ summaries = @api_node.create('async/summary', {'syncs' => asyncids})[:data]['sync_summaries']
445
529
  selected = summaries.find{|s|s['name'].eql?(asyncname)}
446
530
  raise "no such sync: #{asyncname}" if selected.nil?
447
531
  asyncid = selected['snid']
@@ -454,16 +538,16 @@ module Aspera
454
538
  resp = @api_node.read('async/list')[:data]['sync_ids']
455
539
  return { type: :value_list, data: resp, name: 'id' }
456
540
  when :show
457
- resp = @api_node.create('async/summary',pdata)[:data]['sync_summaries']
541
+ resp = @api_node.create('async/summary', pdata)[:data]['sync_summaries']
458
542
  return Main.result_empty if resp.empty?
459
543
  return { type: :object_list, data: resp, fields: %w[snid name local_dir remote_dir] } if asyncid.eql?('ALL')
460
544
  return { type: :single_object, data: resp.first }
461
545
  when :delete
462
- resp = @api_node.create('async/delete',pdata)[:data]
546
+ resp = @api_node.create('async/delete', pdata)[:data]
463
547
  return { type: :single_object, data: resp, name: 'id' }
464
548
  when :bandwidth
465
549
  pdata['seconds'] = 100 # TODO: as parameter with --value
466
- resp = @api_node.create('async/bandwidth',pdata)[:data]
550
+ resp = @api_node.create('async/bandwidth', pdata)[:data]
467
551
  data = resp['bandwidth_data']
468
552
  return Main.result_empty if data.empty?
469
553
  data = data.first[asyncid]['data']
@@ -475,19 +559,19 @@ module Aspera
475
559
  # status int
476
560
  filter = options.get_option(:value)
477
561
  pdata.merge!(filter) unless filter.nil?
478
- resp = @api_node.create('async/files',pdata)[:data]
562
+ resp = @api_node.create('async/files', pdata)[:data]
479
563
  data = resp['sync_files']
480
564
  data = data.first[asyncid] unless data.empty?
481
565
  iteration_data = []
482
566
  skip_ids_persistency = nil
483
- if options.get_option(:once_only,is_type: :mandatory)
567
+ if options.get_option(:once_only, is_type: :mandatory)
484
568
  skip_ids_persistency = PersistencyActionOnce.new(
485
569
  manager: @agents[:persistency],
486
570
  data: iteration_data,
487
571
  id: IdGenerator.from_list([
488
572
  'sync_files',
489
- options.get_option(:url,is_type: :mandatory),
490
- options.get_option(:username,is_type: :mandatory),
573
+ options.get_option(:url, is_type: :mandatory),
574
+ options.get_option(:username, is_type: :mandatory),
491
575
  asyncid]))
492
576
  unless iteration_data.first.nil?
493
577
  data.select!{|l| l['fnid'].to_i > iteration_data.first}
@@ -498,27 +582,50 @@ module Aspera
498
582
  skip_ids_persistency&.save
499
583
  return { type: :object_list, data: data, name: 'id' }
500
584
  when :counters
501
- resp = @api_node.create('async/counters',pdata)[:data]['sync_counters'].first[asyncid].last
585
+ resp = @api_node.create('async/counters', pdata)[:data]['sync_counters'].first[asyncid].last
502
586
  return Main.result_empty if resp.nil?
503
587
  return { type: :single_object, data: resp }
504
588
  end
505
589
  end
506
590
 
507
- ACTIONS = %i[postprocess stream transfer cleanup forward access_key watch_folder service async central asperabrowser basic_token].concat(COMMON_ACTIONS).freeze
591
+ ACTIONS = %i[
592
+ async
593
+ sync
594
+ stream
595
+ transfer
596
+ service
597
+ watch_folder
598
+ central
599
+ asperabrowser
600
+ basic_token].concat(COMMON_ACTIONS).freeze
508
601
 
509
- def execute_action(command=nil,prefix_path=nil)
602
+ def execute_action(command=nil, prefix_path=nil)
510
603
  command ||= options.get_next_command(ACTIONS)
511
604
  case command
512
- when *COMMON_ACTIONS then return execute_simple_common(command,prefix_path)
605
+ when *COMMON_ACTIONS then return execute_simple_common(command, prefix_path)
513
606
  when :async then return execute_async
607
+ when :sync
608
+ # newer api
609
+ sync_command = options.get_next_command(%i[bandwidth counters files start state stop summary].concat(Plugin::ALL_OPS) - %i[modify])
610
+ case sync_command
611
+ when *Plugin::ALL_OPS then return entity_command(sync_command, @api_node, 'asyncs', item_list_key: 'ids')
612
+ else
613
+ parameters = options.get_option(:value)
614
+ asyncs_id = instance_identifier
615
+ if %i[start stop].include?(sync_command)
616
+ @api_node.create("asyncs/#{asyncs_id}/#{sync_command}", parameters)
617
+ return Main.result_status('ok')
618
+ end
619
+ return { type: :single_object, data: @api_node.read("asyncs/#{asyncs_id}/#{sync_command}", parameters)[:data] }
620
+ end
514
621
  when :stream
515
622
  command = options.get_next_command(%i[list create show modify cancel])
516
623
  case command
517
624
  when :list
518
- resp = @api_node.read('ops/transfers',options.get_option(:value))
625
+ resp = @api_node.read('ops/transfers', options.get_option(:value))
519
626
  return { type: :object_list, data: resp[:data], fields: %w[id status] } # TODO: useful?
520
627
  when :create
521
- resp = @api_node.create('streams',options.get_option(:value,is_type: :mandatory))
628
+ resp = @api_node.create('streams', options.get_option(:value, is_type: :mandatory))
522
629
  return { type: :single_object, data: resp[:data] }
523
630
  when :show
524
631
  trid = options.get_next_argument('transfer id')
@@ -526,7 +633,7 @@ module Aspera
526
633
  return { type: :other_struct, data: resp[:data] }
527
634
  when :modify
528
635
  trid = options.get_next_argument('transfer id')
529
- resp = @api_node.update('streams/' + trid,options.get_option(:value,is_type: :mandatory))
636
+ resp = @api_node.update('streams/' + trid, options.get_option(:value, is_type: :mandatory))
530
637
  return { type: :other_struct, data: resp[:data] }
531
638
  when :cancel
532
639
  trid = options.get_next_argument('transfer id')
@@ -545,7 +652,9 @@ module Aspera
545
652
  case command
546
653
  when :list
547
654
  # could use ? subpath: 'transfers'
548
- resp = @api_node.read(res_class_path,options.get_option(:value))
655
+ query = options.get_option(:value) || options.get_option(:query)
656
+ raise 'Query must be a Hash' unless query.nil? || query.is_a?(Hash)
657
+ resp = @api_node.read(res_class_path, query)
549
658
  return {
550
659
  type: :object_list,
551
660
  data: resp[:data],
@@ -560,22 +669,6 @@ module Aspera
560
669
  else
561
670
  raise 'error'
562
671
  end
563
- when :access_key
564
- ak_command = options.get_next_command([Plugin::ALL_OPS,:do].flatten)
565
- case ak_command
566
- when *Plugin::ALL_OPS then return entity_command(ak_command,@api_node,'access_keys',id_default: 'self')
567
- when :do
568
- access_key = options.get_next_argument('access key id')
569
- ak_info=@api_node.read("access_keys/#{access_key}")[:data]
570
- # change API if needed
571
- if !access_key.eql?('self')
572
- secret=config.vault.get(username: access_key)[:secret] #, url: @api_node.params[:base_url] : TODO: better handle vault
573
- @api_node.params[:auth][:username]=access_key
574
- @api_node.params[:auth][:password]=secret
575
- end
576
- command_repo = options.get_next_command(NODE4_COMMANDS)
577
- return execute_node_gen4_command(command_repo,ak_info['root_file_id'])
578
- end
579
672
  when :service
580
673
  command = options.get_next_command(%i[list create delete])
581
674
  if [:delete].include?(command)
@@ -588,7 +681,7 @@ module Aspera
588
681
  when :create
589
682
  # @json:'{"type":"WATCHFOLDERD","run_as":{"user":"user1"}}'
590
683
  params = options.get_next_argument('Run creation data (structure)')
591
- resp = @api_node.create('rund/services',params)
684
+ resp = @api_node.create('rund/services', params)
592
685
  return Main.result_status("#{resp[:data]['id']} created")
593
686
  when :delete
594
687
  @api_node.delete("rund/services/#{svcid}")
@@ -606,15 +699,15 @@ module Aspera
606
699
  @api_node.params[:headers]['X-aspera-WF-version'] = '2017_10_23'
607
700
  case command
608
701
  when :create
609
- resp = @api_node.create(res_class_path,options.get_option(:value,is_type: :mandatory))
702
+ resp = @api_node.create(res_class_path, options.get_option(:value, is_type: :mandatory))
610
703
  return Main.result_status("#{resp[:data]['id']} created")
611
704
  when :list
612
- resp = @api_node.read(res_class_path,options.get_option(:value))
705
+ resp = @api_node.read(res_class_path, options.get_option(:value))
613
706
  return { type: :value_list, data: resp[:data]['ids'], name: 'id' }
614
707
  when :show
615
708
  return { type: :single_object, data: @api_node.read(one_res_path)[:data]}
616
709
  when :modify
617
- @api_node.update(one_res_path,options.get_option(:value,is_type: :mandatory))
710
+ @api_node.update(one_res_path, options.get_option(:value, is_type: :mandatory))
618
711
  return Main.result_status("#{one_res_id} updated")
619
712
  when :delete
620
713
  @api_node.delete(one_res_path)
@@ -634,37 +727,40 @@ module Aspera
634
727
  case command
635
728
  when :list
636
729
  request_data.deep_merge!({'validation' => validation}) unless validation.nil?
637
- resp = @api_node.create('services/rest/transfers/v1/sessions',request_data)
638
- return { type: :object_list, data: resp[:data]['session_info_result']['session_info'],
639
- fields: %w[session_uuid status transport direction bytes_transferred]}
730
+ resp = @api_node.create('services/rest/transfers/v1/sessions', request_data)
731
+ return {
732
+ type: :object_list,
733
+ data: resp[:data]['session_info_result']['session_info'],
734
+ fields: %w[session_uuid status transport direction bytes_transferred]
735
+ }
640
736
  end
641
737
  when :file
642
738
  command = options.get_next_command(%i[list modify])
643
739
  case command
644
740
  when :list
645
741
  request_data.deep_merge!({'validation' => validation}) unless validation.nil?
646
- resp = @api_node.create('services/rest/transfers/v1/files',request_data)[:data]
742
+ resp = @api_node.create('services/rest/transfers/v1/files', request_data)[:data]
647
743
  resp = JSON.parse(resp) if resp.is_a?(String)
648
- Log.dump(:resp,resp)
744
+ Log.dump(:resp, resp)
649
745
  return { type: :object_list, data: resp['file_transfer_info_result']['file_transfer_info'], fields: %w[session_uuid file_id status path]}
650
746
  when :modify
651
747
  request_data.deep_merge!(validation) unless validation.nil?
652
- @api_node.update('services/rest/transfers/v1/files',request_data)
748
+ @api_node.update('services/rest/transfers/v1/files', request_data)
653
749
  return Main.result_status('updated')
654
750
  end
655
751
  end
656
752
  when :asperabrowser
657
753
  browse_params = {
658
- 'nodeUser' => options.get_option(:username,is_type: :mandatory),
659
- 'nodePW' => options.get_option(:password,is_type: :mandatory),
660
- 'nodeURL' => options.get_option(:url,is_type: :mandatory)
754
+ 'nodeUser' => options.get_option(:username, is_type: :mandatory),
755
+ 'nodePW' => options.get_option(:password, is_type: :mandatory),
756
+ 'nodeURL' => options.get_option(:url, is_type: :mandatory)
661
757
  }
662
758
  # encode parameters so that it looks good in url
663
759
  encoded_params = Base64.strict_encode64(Zlib::Deflate.deflate(JSON.generate(browse_params))).gsub(/=+$/, '').tr('+/', '-_').reverse
664
760
  OpenApplication.instance.uri(options.get_option(:asperabrowserurl) + '?goto=' + encoded_params)
665
761
  return Main.result_status('done')
666
762
  when :basic_token
667
- return Main.result_status(Rest.basic_creds(options.get_option(:username,is_type: :mandatory),options.get_option(:password,is_type: :mandatory)))
763
+ return Main.result_status(Rest.basic_creds(options.get_option(:username, is_type: :mandatory), options.get_option(:password, is_type: :mandatory)))
668
764
  end # case command
669
765
  raise 'ERROR: shall not reach this line'
670
766
  end # execute_action