aspera-cli 4.9.0 → 4.11.0

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